@unlev/exeq 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/pdf-builder/DesignerView.tsx
2
- import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef3 } from "react";
2
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect2, useRef as useRef4 } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
5
  // src/types/pdf-builder.ts
@@ -139,18 +139,21 @@ async function renderPdfPages(source, scale = 2) {
139
139
  canvas.height = viewport.height;
140
140
  const ctx = canvas.getContext("2d");
141
141
  await page.render({ canvasContext: ctx, viewport }).promise;
142
+ const unscaledViewport = page.getViewport({ scale: 1 });
142
143
  pages.push({
143
144
  pageNumber: i,
144
145
  dataUrl: canvas.toDataURL("image/png"),
145
146
  width: viewport.width,
146
- height: viewport.height
147
+ height: viewport.height,
148
+ pdfWidth: unscaledViewport.width,
149
+ pdfHeight: unscaledViewport.height
147
150
  });
148
151
  }
149
152
  return pages;
150
153
  }
151
154
 
152
155
  // src/components/pdf-builder/PdfViewer.tsx
153
- import { useRef, useCallback } from "react";
156
+ import { useRef, useCallback, useState } from "react";
154
157
  import { jsx, jsxs } from "react/jsx-runtime";
155
158
  function PdfViewer({
156
159
  pages,
@@ -162,23 +165,69 @@ function PdfViewer({
162
165
  onPageClick,
163
166
  onDropField,
164
167
  onGroupMove,
168
+ onMoveEnd,
165
169
  mode,
166
170
  currentSigner,
167
- renderFieldContent
171
+ renderFieldContent,
172
+ zoom = 1,
173
+ onMarqueeSelect
168
174
  }) {
169
175
  const containerRef = useRef(null);
170
- const handlePageClick = useCallback((e, pageIndex) => {
176
+ const [guides, setGuides] = useState([]);
177
+ const [marquee, setMarquee] = useState(null);
178
+ const marqueeRef = useRef(null);
179
+ const handlePageMouseDown = useCallback((e, pageIndex) => {
171
180
  const target = e.target;
172
181
  if (target.closest(".field-overlay")) return;
173
- if (onPageClick) {
174
- const rect = e.currentTarget.getBoundingClientRect();
175
- const x = (e.clientX - rect.left) / rect.width * 100;
176
- const y = (e.clientY - rect.top) / rect.height * 100;
177
- onPageClick(pageIndex, x, y);
178
- } else {
179
- onSelectField(null);
180
- }
181
- }, [onPageClick, onSelectField]);
182
+ if (mode !== "designer") return;
183
+ e.preventDefault();
184
+ const rect = e.currentTarget.getBoundingClientRect();
185
+ const x = (e.clientX - rect.left) / rect.width * 100;
186
+ const y = (e.clientY - rect.top) / rect.height * 100;
187
+ marqueeRef.current = { startX: x, startY: y, page: pageIndex, didDrag: false };
188
+ const handleMouseMove = (ev) => {
189
+ if (!marqueeRef.current) return;
190
+ const mx = (ev.clientX - rect.left) / rect.width * 100;
191
+ const my = (ev.clientY - rect.top) / rect.height * 100;
192
+ const dx = Math.abs(mx - marqueeRef.current.startX);
193
+ const dy = Math.abs(my - marqueeRef.current.startY);
194
+ if (dx > 1 || dy > 1) {
195
+ marqueeRef.current.didDrag = true;
196
+ setMarquee({
197
+ page: pageIndex,
198
+ x1: Math.min(marqueeRef.current.startX, mx),
199
+ y1: Math.min(marqueeRef.current.startY, my),
200
+ x2: Math.max(marqueeRef.current.startX, mx),
201
+ y2: Math.max(marqueeRef.current.startY, my)
202
+ });
203
+ }
204
+ };
205
+ const handleMouseUp = (ev) => {
206
+ window.removeEventListener("mousemove", handleMouseMove);
207
+ window.removeEventListener("mouseup", handleMouseUp);
208
+ if (marqueeRef.current?.didDrag && onMarqueeSelect) {
209
+ const mx1 = Math.min(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
210
+ const my1 = Math.min(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
211
+ const mx2 = Math.max(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
212
+ const my2 = Math.max(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
213
+ const pageFields = fields.filter((f) => f.page === pageIndex);
214
+ const hits = pageFields.filter((f) => {
215
+ return f.x < mx2 && f.x + f.width > mx1 && f.y < my2 && f.y + f.height > my1;
216
+ }).map((f) => f.id);
217
+ onMarqueeSelect(hits);
218
+ } else if (!marqueeRef.current?.didDrag) {
219
+ if (onPageClick) {
220
+ onPageClick(pageIndex, x, y);
221
+ } else {
222
+ onSelectField(null);
223
+ }
224
+ }
225
+ marqueeRef.current = null;
226
+ setMarquee(null);
227
+ };
228
+ window.addEventListener("mousemove", handleMouseMove);
229
+ window.addEventListener("mouseup", handleMouseUp);
230
+ }, [mode, fields, onPageClick, onSelectField, onMarqueeSelect]);
182
231
  const handleDragOver = useCallback((e) => {
183
232
  if (e.dataTransfer.types.includes("application/exeq-field-type")) {
184
233
  e.preventDefault();
@@ -194,14 +243,14 @@ function PdfViewer({
194
243
  const y = (e.clientY - rect.top) / rect.height * 100;
195
244
  onDropField(pageIndex, x, y, fieldType);
196
245
  }, [onDropField]);
197
- return /* @__PURE__ */ jsx("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
246
+ return /* @__PURE__ */ jsx("div", { className: "pdf-viewer-zoom-wrapper", ref: containerRef, style: zoom !== 1 ? { width: `${zoom * 100}%`, minHeight: "min-content" } : void 0, children: /* @__PURE__ */ jsx("div", { className: "pdf-viewer", style: zoom !== 1 ? { transform: `scale(${zoom})`, transformOrigin: "top left", width: `${100 / zoom}%` } : void 0, children: pages.map((page, pageIndex) => {
198
247
  const pageFields = fields.filter((f) => f.page === pageIndex);
199
248
  return /* @__PURE__ */ jsxs(
200
249
  "div",
201
250
  {
202
251
  className: "pdf-page",
203
252
  style: { aspectRatio: `${page.width} / ${page.height}` },
204
- onClick: (e) => handlePageClick(e, pageIndex),
253
+ onMouseDown: (e) => handlePageMouseDown(e, pageIndex),
205
254
  onDragOver: handleDragOver,
206
255
  onDrop: (e) => handleDrop(e, pageIndex),
207
256
  "data-page": pageIndex,
@@ -225,18 +274,86 @@ function PdfViewer({
225
274
  onMove: onFieldMove,
226
275
  onResize: onFieldResize,
227
276
  onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
277
+ onMoveEnd,
278
+ otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
279
+ setGuides: mode === "designer" ? setGuides : void 0,
280
+ pageIndex,
228
281
  selectedIds: selectedFieldIds,
229
282
  mode,
230
283
  currentSigner,
231
284
  renderContent: renderFieldContent
232
285
  },
233
286
  field.id
234
- ))
287
+ )),
288
+ guides.filter((g) => g.page === pageIndex).map((g, i) => g.x !== void 0 ? /* @__PURE__ */ jsx("div", { className: "snap-guide snap-guide-v", style: { left: `${g.x}%` } }, `gx${i}`) : /* @__PURE__ */ jsx("div", { className: "snap-guide snap-guide-h", style: { top: `${g.y}%` } }, `gy${i}`)),
289
+ marquee && marquee.page === pageIndex && /* @__PURE__ */ jsx("div", { className: "marquee-rect", style: {
290
+ left: `${marquee.x1}%`,
291
+ top: `${marquee.y1}%`,
292
+ width: `${marquee.x2 - marquee.x1}%`,
293
+ height: `${marquee.y2 - marquee.y1}%`
294
+ } })
235
295
  ]
236
296
  },
237
297
  pageIndex
238
298
  );
239
- }) });
299
+ }) }) });
300
+ }
301
+ var SNAP_THRESHOLD = 1.2;
302
+ function snapAndGuide(field, others, page) {
303
+ const fx = field.x, fy = field.y, fw = field.width, fh = field.height;
304
+ const fcx = fx + fw / 2, fcy = fy + fh / 2;
305
+ let bestSnapX = null;
306
+ let bestSnapY = null;
307
+ for (const o of others) {
308
+ const ox = o.x, oy = o.y, ow = o.width, oh = o.height;
309
+ const ocx = ox + ow / 2, ocy = oy + oh / 2;
310
+ const xCandidates = [
311
+ [fx, ox, ox],
312
+ // left ↔ left
313
+ [fx + fw, ox + ow, ox + ow],
314
+ // right ↔ right
315
+ [fcx, ocx, ocx],
316
+ // center ↔ center
317
+ [fx, ox + ow, ox + ow],
318
+ // left ↔ right
319
+ [fx + fw, ox, ox]
320
+ // right ↔ left
321
+ ];
322
+ for (const [fe, te, gx] of xCandidates) {
323
+ const d = Math.abs(fe - te);
324
+ if (d < SNAP_THRESHOLD && (!bestSnapX || d < bestSnapX.dist)) {
325
+ bestSnapX = { offset: te - fe, guideX: gx, dist: d };
326
+ }
327
+ }
328
+ const yCandidates = [
329
+ [fy, oy, oy],
330
+ // top ↔ top
331
+ [fy + fh, oy + oh, oy + oh],
332
+ // bottom ↔ bottom
333
+ [fcy, ocy, ocy],
334
+ // center ↔ center
335
+ [fy, oy + oh, oy + oh],
336
+ // top ↔ bottom
337
+ [fy + fh, oy, oy]
338
+ // bottom ↔ top
339
+ ];
340
+ for (const [fe, te, gy] of yCandidates) {
341
+ const d = Math.abs(fe - te);
342
+ if (d < SNAP_THRESHOLD && (!bestSnapY || d < bestSnapY.dist)) {
343
+ bestSnapY = { offset: te - fe, guideY: gy, dist: d };
344
+ }
345
+ }
346
+ }
347
+ const snappedX = bestSnapX ? fx + bestSnapX.offset : fx;
348
+ const snappedY = bestSnapY ? fy + bestSnapY.offset : fy;
349
+ const guides = [];
350
+ if (bestSnapX) guides.push({ page, x: bestSnapX.guideX });
351
+ if (bestSnapY) guides.push({ page, y: bestSnapY.guideY });
352
+ return {
353
+ x: Math.max(0, Math.min(100 - fw, snappedX)),
354
+ y: Math.max(0, Math.min(100 - fh, snappedY)),
355
+ guides
356
+ };
240
357
  }
241
358
  function FieldOverlayItem({
242
359
  field,
@@ -246,6 +363,10 @@ function FieldOverlayItem({
246
363
  onMove,
247
364
  onResize,
248
365
  onGroupMove,
366
+ onMoveEnd,
367
+ otherFields,
368
+ setGuides,
369
+ pageIndex,
249
370
  selectedIds,
250
371
  mode,
251
372
  currentSigner,
@@ -253,19 +374,27 @@ function FieldOverlayItem({
253
374
  }) {
254
375
  const overlayRef = useRef(null);
255
376
  const dragStartRef = useRef(null);
377
+ const dragPosRef = useRef({ x: field.x, y: field.y });
378
+ const didDragRef = useRef(false);
256
379
  const resizeStartRef = useRef(null);
257
380
  const isRedact = field.type === "blackout" || field.type === "whiteout";
258
381
  const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
259
382
  const isInactiveSigner = mode === "signer" && !isRedact && field.assignee !== currentSigner;
260
- const color = isInactiveSigner ? "#cccccc" : getSignerColor(field.assignee);
383
+ const isFilled = mode === "signer" && isEditable && !isRedact && (field.type === "checkbox" ? true : field.formula ? true : !!field.value);
384
+ const color = isInactiveSigner ? "#cccccc" : isFilled ? "#22c55e" : getSignerColor(field.assignee);
261
385
  const isPreFilled = !isEditable && !!field.value;
262
386
  const handleMouseDown = useCallback((e) => {
263
387
  if (mode !== "designer" || !onMove) return;
264
388
  e.preventDefault();
265
389
  e.stopPropagation();
266
- onSelect(e);
390
+ const alreadyInSelection = selectedIds.has(field.id) && selectedIds.size > 1;
391
+ if (!alreadyInSelection) {
392
+ onSelect(e);
393
+ }
394
+ if (field.locked) return;
267
395
  const pageEl = overlayRef.current?.closest(".pdf-page");
268
396
  if (!pageEl) return;
397
+ didDragRef.current = false;
269
398
  dragStartRef.current = {
270
399
  startX: e.clientX,
271
400
  startY: e.clientY,
@@ -274,6 +403,7 @@ function FieldOverlayItem({
274
403
  };
275
404
  const handleMouseMove = (ev) => {
276
405
  if (!dragStartRef.current) return;
406
+ didDragRef.current = true;
277
407
  const rect = pageEl.getBoundingClientRect();
278
408
  const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
279
409
  const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
@@ -284,19 +414,35 @@ function FieldOverlayItem({
284
414
  } else {
285
415
  const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
286
416
  const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
417
+ dragPosRef.current = { x: newX, y: newY };
287
418
  onMove(field.id, field.page, newX, newY);
419
+ if (ev.shiftKey && otherFields && setGuides && pageIndex !== void 0) {
420
+ const { guides: g } = snapAndGuide({ x: newX, y: newY, width: field.width, height: field.height }, otherFields, pageIndex);
421
+ setGuides(g);
422
+ } else {
423
+ setGuides?.([]);
424
+ }
288
425
  }
289
426
  };
290
- const handleMouseUp = () => {
427
+ const handleMouseUp = (ev) => {
428
+ if (ev.shiftKey && didDragRef.current && otherFields && onMove && pageIndex !== void 0 && !isMultiSelected) {
429
+ const pos = dragPosRef.current;
430
+ const snap = snapAndGuide({ x: pos.x, y: pos.y, width: field.width, height: field.height }, otherFields, pageIndex);
431
+ if (snap.x !== pos.x || snap.y !== pos.y) {
432
+ onMove(field.id, field.page, snap.x, snap.y);
433
+ }
434
+ }
291
435
  dragStartRef.current = null;
292
436
  window.removeEventListener("mousemove", handleMouseMove);
293
437
  window.removeEventListener("mouseup", handleMouseUp);
438
+ setGuides?.([]);
439
+ onMoveEnd?.();
294
440
  };
295
441
  window.addEventListener("mousemove", handleMouseMove);
296
442
  window.addEventListener("mouseup", handleMouseUp);
297
- }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
443
+ }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
298
444
  const handleResizeMouseDown = useCallback((e) => {
299
- if (mode !== "designer" || !onResize) return;
445
+ if (mode !== "designer" || !onResize || field.locked) return;
300
446
  e.preventDefault();
301
447
  e.stopPropagation();
302
448
  const pageEl = overlayRef.current?.closest(".pdf-page");
@@ -320,15 +466,16 @@ function FieldOverlayItem({
320
466
  resizeStartRef.current = null;
321
467
  window.removeEventListener("mousemove", handleMouseMove);
322
468
  window.removeEventListener("mouseup", handleMouseUp);
469
+ onMoveEnd?.();
323
470
  };
324
471
  window.addEventListener("mousemove", handleMouseMove);
325
472
  window.addEventListener("mouseup", handleMouseUp);
326
- }, [field, mode, onResize]);
473
+ }, [field, mode, onResize, onMoveEnd]);
327
474
  return /* @__PURE__ */ jsxs(
328
475
  "div",
329
476
  {
330
477
  ref: overlayRef,
331
- className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""}`,
478
+ className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""} ${field.locked ? "locked" : ""}`,
332
479
  style: {
333
480
  left: `${field.x}%`,
334
481
  top: `${field.y}%`,
@@ -337,18 +484,18 @@ function FieldOverlayItem({
337
484
  borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
338
485
  borderStyle: isRedact ? "dashed" : "solid",
339
486
  backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isInactiveSigner ? "#f5f5f5" : isSelected ? `${color}22` : `${color}11`,
340
- cursor: mode === "designer" ? "move" : isInactiveSigner ? "default" : "default",
487
+ cursor: mode === "designer" ? field.locked ? "not-allowed" : "move" : "default",
341
488
  pointerEvents: mode === "signer" && (isRedact || isInactiveSigner) ? "none" : void 0
342
489
  },
343
490
  onClick: (e) => {
344
491
  e.stopPropagation();
345
- onSelect(e);
492
+ if (!didDragRef.current) onSelect(e);
346
493
  },
347
494
  onMouseDown: handleMouseDown,
348
495
  children: [
349
496
  mode === "designer" && !isRedact && /* @__PURE__ */ jsx("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
350
497
  renderContent ? renderContent(field) : /* @__PURE__ */ jsx("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
351
- mode === "designer" && isSelected && /* @__PURE__ */ jsx(
498
+ mode === "designer" && isSelected && !field.locked && /* @__PURE__ */ jsx(
352
499
  "div",
353
500
  {
354
501
  className: "field-resize-handle",
@@ -361,127 +508,320 @@ function FieldOverlayItem({
361
508
  }
362
509
 
363
510
  // src/components/pdf-builder/FieldPropertyPanel.tsx
364
- import { useState } from "react";
365
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
511
+ import { useState as useState2 } from "react";
512
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
366
513
  var INK_COLORS = [
367
514
  { value: "#000000", label: "Black" },
368
515
  { value: "#1a56db", label: "Blue" }
369
516
  ];
370
- function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
517
+ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillContent, pageSize }) {
371
518
  const color = getSignerColor(field.assignee);
372
519
  const isRedactField = field.type === "blackout" || field.type === "whiteout";
373
520
  const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
374
521
  const showInkColor = field.type === "text" || field.type === "dropdown" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
375
- const [newOption, setNewOption] = useState("");
522
+ const [newOption, setNewOption] = useState2("");
376
523
  return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
377
524
  /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
378
- /* @__PURE__ */ jsx2("h3", { style: { color }, children: field.label }),
379
- /* @__PURE__ */ jsx2("button", { onClick: () => onDelete(field.id), className: "panel-delete-btn", children: "Delete" })
380
- ] }),
381
- /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
382
- /* @__PURE__ */ jsx2("label", { children: "Label" }),
383
525
  /* @__PURE__ */ jsx2(
384
526
  "input",
385
527
  {
386
- type: "text",
528
+ className: "panel-header-label",
529
+ style: { color },
387
530
  value: field.label,
388
531
  onChange: (e) => onUpdate(field.id, { label: e.target.value })
389
532
  }
390
- )
391
- ] }),
392
- !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
393
- /* @__PURE__ */ jsx2("label", { children: "Field Type" }),
394
- /* @__PURE__ */ jsxs2(
395
- "select",
396
- {
397
- value: field.type,
398
- onChange: (e) => onUpdate(field.id, { type: e.target.value }),
399
- children: [
400
- /* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
401
- /* @__PURE__ */ jsx2("option", { value: "dropdown", children: "Dropdown" }),
402
- /* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
403
- /* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
404
- /* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
405
- /* @__PURE__ */ jsx2("option", { value: "initials", children: "Initials" })
406
- ]
407
- }
408
- )
409
- ] }),
410
- field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
411
- /* @__PURE__ */ jsx2("label", { children: "Text Type" }),
412
- /* @__PURE__ */ jsxs2(
413
- "select",
414
- {
415
- value: field.textSubtype || "freeform",
416
- onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }),
417
- children: [
418
- /* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
419
- /* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
420
- /* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
421
- /* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
422
- /* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
423
- ]
424
- }
425
- )
533
+ ),
534
+ /* @__PURE__ */ jsxs2("div", { className: "panel-header-actions", children: [
535
+ /* @__PURE__ */ jsx2(
536
+ "button",
537
+ {
538
+ onClick: () => onUpdate(field.id, { locked: !field.locked }),
539
+ className: `panel-icon-btn ${field.locked ? "active" : ""}`,
540
+ title: field.locked ? "Unlock" : "Lock",
541
+ children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: field.locked ? /* @__PURE__ */ jsxs2(Fragment, { children: [
542
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
543
+ /* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0V6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
544
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
545
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
546
+ /* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
547
+ ] }) })
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsx2("button", { onClick: () => onDelete(field.id), className: "panel-icon-btn panel-icon-btn-danger", title: "Delete", children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: /* @__PURE__ */ jsx2("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" }) }) })
551
+ ] })
426
552
  ] }),
427
- !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
428
- /* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
553
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
554
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Assigned To" }),
429
555
  /* @__PURE__ */ jsx2(
430
556
  "select",
431
557
  {
558
+ className: "panel-assignee-select",
432
559
  value: field.assignee,
433
560
  onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
561
+ style: { borderColor: color, color },
434
562
  children: signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
435
563
  }
436
564
  )
437
565
  ] }),
438
- !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
439
- /* @__PURE__ */ jsx2("label", { children: "Placeholder" }),
440
- /* @__PURE__ */ jsx2(
441
- "input",
442
- {
443
- type: "text",
444
- value: field.placeholder,
445
- onChange: (e) => onUpdate(field.id, { placeholder: e.target.value })
446
- }
447
- )
566
+ /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
567
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Content" }),
568
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
569
+ /* @__PURE__ */ jsx2("label", { children: "Field Type" }),
570
+ /* @__PURE__ */ jsxs2("select", { value: field.type, onChange: (e) => onUpdate(field.id, { type: e.target.value }), children: [
571
+ /* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
572
+ /* @__PURE__ */ jsx2("option", { value: "dropdown", children: "Dropdown" }),
573
+ /* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
574
+ /* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
575
+ /* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
576
+ /* @__PURE__ */ jsx2("option", { value: "initials", children: "Initials" })
577
+ ] })
578
+ ] }),
579
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
580
+ /* @__PURE__ */ jsx2("label", { children: "Text Type" }),
581
+ /* @__PURE__ */ jsxs2("select", { value: field.textSubtype || "freeform", onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }), children: [
582
+ /* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
583
+ /* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
584
+ /* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
585
+ /* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
586
+ /* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
587
+ ] })
588
+ ] }),
589
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
590
+ /* @__PURE__ */ jsx2("label", { children: "Placeholder" }),
591
+ /* @__PURE__ */ jsx2("input", { type: "text", value: field.placeholder, onChange: (e) => onUpdate(field.id, { placeholder: e.target.value }) })
592
+ ] }),
593
+ !isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
594
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", checked: field.required, onChange: (e) => onUpdate(field.id, { required: e.target.checked }) }),
595
+ "Required"
596
+ ] }) }),
597
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
598
+ /* @__PURE__ */ jsx2("label", { children: "Formula" }),
599
+ /* @__PURE__ */ jsx2("input", { type: "text", value: field.formula || "", onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }), placeholder: "e.g. {{Date Field | month}}" }),
600
+ field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Auto-computed. Signer cannot edit." })
601
+ ] }),
602
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
603
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
604
+ /* @__PURE__ */ jsx2("label", { children: "Min Chars" }),
605
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.minLength || 0, onChange: (e) => onUpdate(field.id, { minLength: Number(e.target.value) }) })
606
+ ] }),
607
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
608
+ /* @__PURE__ */ jsx2("label", { children: "Max Chars" }),
609
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
610
+ ] })
611
+ ] }),
612
+ (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
613
+ /* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
614
+ /* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
615
+ (field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
616
+ /* @__PURE__ */ jsx2("span", { children: opt }),
617
+ /* @__PURE__ */ jsx2("button", { className: "panel-option-remove", onClick: () => {
618
+ const updated = (field.options || []).filter((_, j) => j !== i);
619
+ onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
620
+ }, children: "\xD7" })
621
+ ] }, i)),
622
+ /* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
623
+ /* @__PURE__ */ jsx2("input", { type: "text", value: newOption, onChange: (e) => setNewOption(e.target.value), onKeyDown: (e) => {
624
+ if (e.key === "Enter" && newOption.trim()) {
625
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
626
+ setNewOption("");
627
+ }
628
+ }, placeholder: "Add option..." }),
629
+ /* @__PURE__ */ jsx2("button", { onClick: () => {
630
+ if (newOption.trim()) {
631
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
632
+ setNewOption("");
633
+ }
634
+ }, children: "+" })
635
+ ] })
636
+ ] }),
637
+ field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
638
+ field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
639
+ ] }),
640
+ prefillContent
448
641
  ] }),
449
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
450
- /* @__PURE__ */ jsx2("label", { children: "Formula" }),
451
- /* @__PURE__ */ jsx2(
452
- "input",
642
+ (isTextField || showInkColor) && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
643
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Style" }),
644
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
645
+ /* @__PURE__ */ jsx2("label", { children: "Font" }),
646
+ /* @__PURE__ */ jsx2("select", { value: field.fontFamily || "Helvetica", onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }), children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value)) })
647
+ ] }),
648
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
649
+ /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
650
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
651
+ ] }),
652
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
653
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
654
+ /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
655
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "20", step: "0.5", value: field.letterSpacing || 0, onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) }) })
656
+ ] }),
657
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
658
+ /* @__PURE__ */ jsx2("label", { children: "Line Height" }),
659
+ /* @__PURE__ */ jsx2("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) }) })
660
+ ] })
661
+ ] }),
662
+ showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
663
+ /* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
664
+ /* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2("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)) })
665
+ ] })
666
+ ] }),
667
+ /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
668
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Position & Size" }),
669
+ (() => {
670
+ const pw = pageSize?.width || 612;
671
+ const ph = pageSize?.height || 792;
672
+ const toPt = (pct, dim) => Math.round(pct / 100 * dim * 10) / 10;
673
+ const fromPt = (pt, dim) => pt / dim * 100;
674
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
675
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
676
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
677
+ /* @__PURE__ */ jsx2("label", { children: "X (pt)" }),
678
+ /* @__PURE__ */ jsx2("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) }) })
679
+ ] }),
680
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
681
+ /* @__PURE__ */ jsx2("label", { children: "Y (pt)" }),
682
+ /* @__PURE__ */ jsx2("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) }) })
683
+ ] })
684
+ ] }),
685
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
686
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
687
+ /* @__PURE__ */ jsx2("label", { children: "Width (pt)" }),
688
+ /* @__PURE__ */ jsx2("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) }) })
689
+ ] }),
690
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
691
+ /* @__PURE__ */ jsx2("label", { children: "Height (pt)" }),
692
+ /* @__PURE__ */ jsx2("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) }) })
693
+ ] })
694
+ ] }),
695
+ /* @__PURE__ */ jsxs2("div", { className: "panel-hint", children: [
696
+ pw,
697
+ " \xD7 ",
698
+ ph,
699
+ " pt \xB7 Page ",
700
+ field.page + 1,
701
+ " \xB7 ",
702
+ field.id.slice(0, 8)
703
+ ] })
704
+ ] });
705
+ })()
706
+ ] })
707
+ ] });
708
+ }
709
+ function sharedValue(items) {
710
+ if (items.length === 0) return void 0;
711
+ return items.every((v) => v === items[0]) ? items[0] : void 0;
712
+ }
713
+ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete }) {
714
+ const count = fields.length;
715
+ const hasText = fields.some((f) => f.type === "text" || f.type === "dropdown" || f.type === "signed-date");
716
+ const allNonRedact = fields.every((f) => f.type !== "blackout" && f.type !== "whiteout");
717
+ const sharedAssignee = sharedValue(fields.map((f) => f.assignee));
718
+ const sharedRequired = sharedValue(fields.map((f) => f.required));
719
+ const sharedFontSize = sharedValue(fields.map((f) => f.fontSize));
720
+ const sharedFontFamily = sharedValue(fields.map((f) => f.fontFamily || "Helvetica"));
721
+ const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
722
+ const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
723
+ const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
724
+ return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
725
+ /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
726
+ /* @__PURE__ */ jsxs2("h3", { children: [
727
+ count,
728
+ " fields selected"
729
+ ] }),
730
+ /* @__PURE__ */ jsx2("button", { onClick: onDelete, className: "panel-delete-btn", children: "Delete All" })
731
+ ] }),
732
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
733
+ /* @__PURE__ */ jsx2("label", { children: "Align" }),
734
+ /* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
735
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("left"), title: "Align left edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
736
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
737
+ /* @__PURE__ */ jsx2("rect", { x: "4", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
738
+ /* @__PURE__ */ jsx2("rect", { x: "4", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
739
+ ] }) }),
740
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("center-h"), title: "Align centers horizontally", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
741
+ /* @__PURE__ */ jsx2("rect", { x: "6.25", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
742
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "10", height: "3", fill: "currentColor", opacity: "0.5" }),
743
+ /* @__PURE__ */ jsx2("rect", { x: "3.5", y: "7", width: "7", height: "3", fill: "currentColor", opacity: "0.5" })
744
+ ] }) }),
745
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("right"), title: "Align right edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
746
+ /* @__PURE__ */ jsx2("rect", { x: "11.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
747
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
748
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
749
+ ] }) }),
750
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("top"), title: "Align top edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
751
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "1", width: "14", height: "1.5", fill: "currentColor" }),
752
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
753
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "4", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
754
+ ] }) }),
755
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("center-v"), title: "Align centers vertically", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
756
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "6.25", width: "14", height: "1.5", fill: "currentColor" }),
757
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "1", width: "3", height: "12", fill: "currentColor", opacity: "0.5" }),
758
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "3", width: "3", height: "8", fill: "currentColor", opacity: "0.5" })
759
+ ] }) }),
760
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("bottom"), title: "Align bottom edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
761
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "11.5", width: "14", height: "1.5", fill: "currentColor" }),
762
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
763
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "5", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
764
+ ] }) })
765
+ ] }),
766
+ count > 2 && /* @__PURE__ */ jsxs2(Fragment, { children: [
767
+ /* @__PURE__ */ jsx2("label", { style: { marginTop: "0.4rem" }, children: "Distribute" }),
768
+ /* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
769
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-h"), title: "Distribute horizontally", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
770
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
771
+ /* @__PURE__ */ jsx2("rect", { x: "12.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
772
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "3", width: "4", height: "8", fill: "currentColor", opacity: "0.5" })
773
+ ] }) }),
774
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-v"), title: "Distribute vertically", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
775
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "14", height: "1.5", fill: "currentColor" }),
776
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "12.5", width: "14", height: "1.5", fill: "currentColor" }),
777
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "5", width: "8", height: "4", fill: "currentColor", opacity: "0.5" })
778
+ ] }) })
779
+ ] })
780
+ ] })
781
+ ] }),
782
+ allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
783
+ /* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
784
+ /* @__PURE__ */ jsxs2(
785
+ "select",
453
786
  {
454
- type: "text",
455
- value: field.formula || "",
456
- onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }),
457
- placeholder: "e.g. {{Date Field | month}}"
787
+ value: sharedAssignee || "",
788
+ onChange: (e) => onUpdate({ assignee: e.target.value }),
789
+ children: [
790
+ !sharedAssignee && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
791
+ signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
792
+ ]
458
793
  }
459
- ),
460
- field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
794
+ )
461
795
  ] }),
462
- !isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
796
+ allNonRedact && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
463
797
  /* @__PURE__ */ jsx2(
464
798
  "input",
465
799
  {
466
800
  type: "checkbox",
467
- checked: field.required,
468
- onChange: (e) => onUpdate(field.id, { required: e.target.checked })
801
+ checked: sharedRequired ?? false,
802
+ ref: (el) => {
803
+ if (el) el.indeterminate = sharedRequired === void 0;
804
+ },
805
+ onChange: (e) => onUpdate({ required: e.target.checked })
469
806
  }
470
807
  ),
471
808
  "Required"
472
809
  ] }) }),
473
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
810
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
474
811
  /* @__PURE__ */ jsx2("label", { children: "Font" }),
475
- /* @__PURE__ */ jsx2(
812
+ /* @__PURE__ */ jsxs2(
476
813
  "select",
477
814
  {
478
- value: field.fontFamily || "Helvetica",
479
- onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }),
480
- children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
815
+ value: sharedFontFamily || "",
816
+ onChange: (e) => onUpdate({ fontFamily: e.target.value }),
817
+ children: [
818
+ !sharedFontFamily && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
819
+ FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
820
+ ]
481
821
  }
482
822
  )
483
823
  ] }),
484
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
824
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
485
825
  /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
486
826
  /* @__PURE__ */ jsx2(
487
827
  "input",
@@ -489,12 +829,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
489
829
  type: "number",
490
830
  min: "6",
491
831
  max: "72",
492
- value: field.fontSize,
493
- onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) })
832
+ value: sharedFontSize ?? "",
833
+ placeholder: "Mixed",
834
+ onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
494
835
  }
495
836
  )
496
837
  ] }),
497
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
838
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
498
839
  /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
499
840
  /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
500
841
  /* @__PURE__ */ jsx2(
@@ -504,8 +845,9 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
504
845
  min: "0",
505
846
  max: "20",
506
847
  step: "0.5",
507
- value: field.letterSpacing || 0,
508
- onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) })
848
+ value: sharedLetterSpacing ?? "",
849
+ placeholder: "Mixed",
850
+ onChange: (e) => onUpdate({ letterSpacing: Number(e.target.value) })
509
851
  }
510
852
  )
511
853
  ] }),
@@ -518,93 +860,31 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
518
860
  min: "0.8",
519
861
  max: "3",
520
862
  step: "0.1",
521
- value: field.lineHeight || 1.2,
522
- onChange: (e) => onUpdate(field.id, { lineHeight: Number(e.target.value) })
863
+ value: sharedLineHeight ?? "",
864
+ placeholder: "Mixed",
865
+ onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
523
866
  }
524
867
  )
525
868
  ] })
526
869
  ] }),
527
- field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
528
- /* @__PURE__ */ jsx2("label", { children: "Max Characters (0 = unlimited)" }),
529
- /* @__PURE__ */ jsx2(
530
- "input",
531
- {
532
- type: "number",
533
- min: "0",
534
- max: "9999",
535
- value: field.maxLength || 0,
536
- onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) })
537
- }
538
- )
539
- ] }),
540
- showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
870
+ allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
541
871
  /* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
542
872
  /* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2(
543
873
  "button",
544
874
  {
545
- className: `ink-color-swatch ${(field.inkColor || "#000000") === c.value ? "active" : ""}`,
875
+ className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
546
876
  style: { backgroundColor: c.value },
547
- onClick: () => onUpdate(field.id, { inkColor: c.value }),
877
+ onClick: () => onUpdate({ inkColor: c.value }),
548
878
  title: c.label
549
879
  },
550
880
  c.value
551
881
  )) })
552
- ] }),
553
- (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
554
- /* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
555
- /* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
556
- (field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
557
- /* @__PURE__ */ jsx2("span", { children: opt }),
558
- /* @__PURE__ */ jsx2(
559
- "button",
560
- {
561
- className: "panel-option-remove",
562
- onClick: () => {
563
- const updated = (field.options || []).filter((_, j) => j !== i);
564
- onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
565
- },
566
- children: "\xD7"
567
- }
568
- )
569
- ] }, i)),
570
- /* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
571
- /* @__PURE__ */ jsx2(
572
- "input",
573
- {
574
- type: "text",
575
- value: newOption,
576
- onChange: (e) => setNewOption(e.target.value),
577
- onKeyDown: (e) => {
578
- if (e.key === "Enter" && newOption.trim()) {
579
- onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
580
- setNewOption("");
581
- }
582
- },
583
- placeholder: "Add option..."
584
- }
585
- ),
586
- /* @__PURE__ */ jsx2(
587
- "button",
588
- {
589
- onClick: () => {
590
- if (newOption.trim()) {
591
- onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
592
- setNewOption("");
593
- }
594
- },
595
- children: "+"
596
- }
597
- )
598
- ] })
599
- ] }),
600
- field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
601
- field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
602
882
  ] })
603
883
  ] });
604
884
  }
605
885
 
606
886
  // src/components/pdf-builder/SignatureCanvas.tsx
607
- import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect } from "react";
887
+ import { useRef as useRef2, useState as useState3, useCallback as useCallback2, useEffect } from "react";
608
888
  import { getStroke } from "perfect-freehand";
609
889
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
610
890
  function getSvgPathFromStroke(stroke) {
@@ -629,9 +909,9 @@ function SignatureCanvas({
629
909
  inkColor = "#000000"
630
910
  }) {
631
911
  const canvasRef = useRef2(null);
632
- const [paths, setPaths] = useState2([]);
633
- const [currentPath, setCurrentPath] = useState2(null);
634
- const [isEmpty, setIsEmpty] = useState2(!initialValue);
912
+ const [paths, setPaths] = useState3([]);
913
+ const [currentPath, setCurrentPath] = useState3(null);
914
+ const [isEmpty, setIsEmpty] = useState3(!initialValue);
635
915
  useEffect(() => {
636
916
  if (initialValue && canvasRef.current) {
637
917
  const ctx = canvasRef.current.getContext("2d");
@@ -743,8 +1023,77 @@ function isValidApiKey(key) {
743
1023
  return VALID_KEYS.has(key);
744
1024
  }
745
1025
 
1026
+ // src/hooks/useHistory.ts
1027
+ import { useState as useState4, useCallback as useCallback3, useRef as useRef3 } from "react";
1028
+ function useHistory(initialState, maxHistory = 50) {
1029
+ const [state, setState] = useState4({
1030
+ past: [],
1031
+ present: initialState,
1032
+ future: []
1033
+ });
1034
+ const skipRef = useRef3(false);
1035
+ const set = useCallback3((updater) => {
1036
+ setState((prev) => {
1037
+ const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
1038
+ if (newPresent === prev.present) return prev;
1039
+ if (skipRef.current) {
1040
+ skipRef.current = false;
1041
+ return { ...prev, present: newPresent };
1042
+ }
1043
+ return {
1044
+ past: [...prev.past.slice(-maxHistory), prev.present],
1045
+ present: newPresent,
1046
+ future: []
1047
+ };
1048
+ });
1049
+ }, [maxHistory]);
1050
+ const setWithoutHistory = useCallback3((updater) => {
1051
+ skipRef.current = true;
1052
+ set(updater);
1053
+ }, [set]);
1054
+ const snapshot = useCallback3(() => {
1055
+ setState((prev) => ({
1056
+ past: [...prev.past.slice(-maxHistory), prev.present],
1057
+ present: prev.present,
1058
+ future: []
1059
+ }));
1060
+ }, [maxHistory]);
1061
+ const undo = useCallback3(() => {
1062
+ setState((prev) => {
1063
+ if (prev.past.length === 0) return prev;
1064
+ const previous = prev.past[prev.past.length - 1];
1065
+ return {
1066
+ past: prev.past.slice(0, -1),
1067
+ present: previous,
1068
+ future: [prev.present, ...prev.future]
1069
+ };
1070
+ });
1071
+ }, []);
1072
+ const redo = useCallback3(() => {
1073
+ setState((prev) => {
1074
+ if (prev.future.length === 0) return prev;
1075
+ const next = prev.future[0];
1076
+ return {
1077
+ past: [...prev.past, prev.present],
1078
+ present: next,
1079
+ future: prev.future.slice(1)
1080
+ };
1081
+ });
1082
+ }, []);
1083
+ return {
1084
+ state: state.present,
1085
+ set,
1086
+ setWithoutHistory,
1087
+ snapshot,
1088
+ undo,
1089
+ redo,
1090
+ canUndo: state.past.length > 0,
1091
+ canRedo: state.future.length > 0
1092
+ };
1093
+ }
1094
+
746
1095
  // src/components/pdf-builder/DesignerView.tsx
747
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1096
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
748
1097
  var FIELD_TYPE_META = [
749
1098
  { type: "text", label: "Text", icon: "T" },
750
1099
  { type: "dropdown", label: "Dropdown", icon: "\u25BE" },
@@ -773,23 +1122,27 @@ function DesignerView({
773
1122
  ] })
774
1123
  ] }) });
775
1124
  }
776
- const [pages, setPages] = useState3([]);
777
- const [fields, setFields] = useState3(initialTemplate?.fields ?? []);
778
- const [selectedFieldIds, setSelectedFieldIds] = useState3(/* @__PURE__ */ new Set());
779
- const [signerRoles, setSignerRoles] = useState3(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
780
- const [activeRole, setActiveRole] = useState3("Sender");
781
- const [activeFieldType, setActiveFieldType] = useState3("text");
782
- const [loading, setLoading] = useState3(false);
783
- const [pdfSource, setPdfSource] = useState3(null);
784
- const [rightTab, setRightTab] = useState3("properties");
785
- const [isAddingRole, setIsAddingRole] = useState3(false);
786
- const [newRoleName, setNewRoleName] = useState3("");
787
- const [draggingFieldType, setDraggingFieldType] = useState3(null);
788
- const [panelWidth, setPanelWidth] = useState3(380);
789
- const [clipboardFields, setClipboardFields] = useState3([]);
790
- const dragGhostRef = useRef3(null);
791
- const resizingRef = useRef3(false);
792
- const lastStylesRef = useRef3({});
1125
+ const [pages, setPages] = useState5([]);
1126
+ const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, snapshot: snapshotFields, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
1127
+ const [selectedFieldIds, setSelectedFieldIds] = useState5(/* @__PURE__ */ new Set());
1128
+ const [signerRoles, setSignerRoles] = useState5(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
1129
+ const [activeRole, setActiveRole] = useState5("Sender");
1130
+ const [activeFieldType, setActiveFieldType] = useState5("text");
1131
+ const [loading, setLoading] = useState5(false);
1132
+ const [pdfSource, setPdfSource] = useState5(null);
1133
+ const [rightTab, setRightTab] = useState5("properties");
1134
+ const [isAddingRole, setIsAddingRole] = useState5(false);
1135
+ const [newRoleName, setNewRoleName] = useState5("");
1136
+ const [draggingFieldType, setDraggingFieldType] = useState5(null);
1137
+ const [panelWidth, setPanelWidth] = useState5(380);
1138
+ const [zoom, setZoom] = useState5(1);
1139
+ const [isPanning, setIsPanning] = useState5(false);
1140
+ const panRef = useRef4(null);
1141
+ const pdfAreaRef = useRef4(null);
1142
+ const [clipboardFields, setClipboardFields] = useState5([]);
1143
+ const dragGhostRef = useRef4(null);
1144
+ const resizingRef = useRef4(false);
1145
+ const lastStylesRef = useRef4({});
793
1146
  useEffect2(() => {
794
1147
  const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
795
1148
  if (pdfUrl) {
@@ -814,7 +1167,7 @@ function DesignerView({
814
1167
  window.addEventListener("message", handleMessage);
815
1168
  return () => window.removeEventListener("message", handleMessage);
816
1169
  }, [initialPdfUrl, initialTemplate]);
817
- const loadPdf = useCallback3(async (source) => {
1170
+ const loadPdf = useCallback4(async (source) => {
818
1171
  setLoading(true);
819
1172
  try {
820
1173
  const rendered = await renderPdfPages(source);
@@ -826,7 +1179,7 @@ function DesignerView({
826
1179
  setLoading(false);
827
1180
  }
828
1181
  }, []);
829
- const handleFileUpload = useCallback3((e) => {
1182
+ const handleFileUpload = useCallback4((e) => {
830
1183
  const file = e.target.files?.[0];
831
1184
  if (!file) return;
832
1185
  const reader = new FileReader();
@@ -835,7 +1188,7 @@ function DesignerView({
835
1188
  };
836
1189
  reader.readAsArrayBuffer(file);
837
1190
  }, [loadPdf]);
838
- const handlePageClick = useCallback3((page, x, y) => {
1191
+ const handlePageClick = useCallback4((page, x, y) => {
839
1192
  const field = createField(activeFieldType, activeRole, page, x, y, fields);
840
1193
  const sticky = lastStylesRef.current[activeFieldType];
841
1194
  const styledField = sticky ? { ...field, ...sticky } : field;
@@ -843,11 +1196,14 @@ function DesignerView({
843
1196
  setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
844
1197
  setRightTab("properties");
845
1198
  }, [activeFieldType, activeRole, fields]);
846
- const handleFieldMove = useCallback3((id, page, x, y) => {
847
- setFields((prev) => prev.map((f) => f.id === id ? { ...f, page, x, y } : f));
848
- }, []);
849
- const handleFieldResize = useCallback3((id, width, height) => {
850
- setFields((prev) => {
1199
+ const handleFieldMove = useCallback4((id, page, x, y) => {
1200
+ setFieldsSilent((prev) => prev.map((f) => {
1201
+ if (f.id !== id || f.locked) return f;
1202
+ return { ...f, page, x, y };
1203
+ }));
1204
+ }, [setFieldsSilent]);
1205
+ const handleFieldResize = useCallback4((id, width, height) => {
1206
+ setFieldsSilent((prev) => {
851
1207
  const field = prev.find((f) => f.id === id);
852
1208
  if (field) {
853
1209
  const existing = lastStylesRef.current[field.type] || {};
@@ -855,8 +1211,8 @@ function DesignerView({
855
1211
  }
856
1212
  return prev.map((f) => f.id === id ? { ...f, width, height } : f);
857
1213
  });
858
- }, []);
859
- const handleFieldUpdate = useCallback3((id, updates) => {
1214
+ }, [setFieldsSilent]);
1215
+ const handleFieldUpdate = useCallback4((id, updates) => {
860
1216
  setFields((prev) => {
861
1217
  if (updates.label !== void 0) {
862
1218
  const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
@@ -876,7 +1232,75 @@ function DesignerView({
876
1232
  return updated;
877
1233
  });
878
1234
  }, []);
879
- const handleFieldDelete = useCallback3((id) => {
1235
+ const handleBulkUpdate = useCallback4((updates) => {
1236
+ setFields((prev) => prev.map((f) => selectedFieldIds.has(f.id) ? { ...f, ...updates } : f));
1237
+ }, [selectedFieldIds]);
1238
+ const handleAlign = useCallback4((type) => {
1239
+ setFields((prev) => {
1240
+ const selected = prev.filter((f) => selectedFieldIds.has(f.id));
1241
+ if (selected.length < 2) return prev;
1242
+ let updates = {};
1243
+ if (type === "left") {
1244
+ const minX = Math.min(...selected.map((f) => f.x));
1245
+ selected.forEach((f) => {
1246
+ updates[f.id] = { x: minX };
1247
+ });
1248
+ } else if (type === "right") {
1249
+ const maxRight = Math.max(...selected.map((f) => f.x + f.width));
1250
+ selected.forEach((f) => {
1251
+ updates[f.id] = { x: maxRight - f.width };
1252
+ });
1253
+ } else if (type === "top") {
1254
+ const minY = Math.min(...selected.map((f) => f.y));
1255
+ selected.forEach((f) => {
1256
+ updates[f.id] = { y: minY };
1257
+ });
1258
+ } else if (type === "bottom") {
1259
+ const maxBottom = Math.max(...selected.map((f) => f.y + f.height));
1260
+ selected.forEach((f) => {
1261
+ updates[f.id] = { y: maxBottom - f.height };
1262
+ });
1263
+ } else if (type === "center-h") {
1264
+ const centers = selected.map((f) => f.x + f.width / 2);
1265
+ const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
1266
+ selected.forEach((f) => {
1267
+ updates[f.id] = { x: avgCenter - f.width / 2 };
1268
+ });
1269
+ } else if (type === "center-v") {
1270
+ const centers = selected.map((f) => f.y + f.height / 2);
1271
+ const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
1272
+ selected.forEach((f) => {
1273
+ updates[f.id] = { y: avgCenter - f.height / 2 };
1274
+ });
1275
+ } else if (type === "distribute-h") {
1276
+ const sorted = [...selected].sort((a, b) => a.x - b.x);
1277
+ const first = sorted[0], last = sorted[sorted.length - 1];
1278
+ const totalSpan = last.x + last.width - first.x;
1279
+ const totalFieldWidth = sorted.reduce((s, f) => s + f.width, 0);
1280
+ const gap = (totalSpan - totalFieldWidth) / (sorted.length - 1);
1281
+ let cx = first.x;
1282
+ sorted.forEach((f) => {
1283
+ updates[f.id] = { x: cx };
1284
+ cx += f.width + gap;
1285
+ });
1286
+ } else if (type === "distribute-v") {
1287
+ const sorted = [...selected].sort((a, b) => a.y - b.y);
1288
+ const first = sorted[0], last = sorted[sorted.length - 1];
1289
+ const totalSpan = last.y + last.height - first.y;
1290
+ const totalFieldHeight = sorted.reduce((s, f) => s + f.height, 0);
1291
+ const gap = (totalSpan - totalFieldHeight) / (sorted.length - 1);
1292
+ let cy = first.y;
1293
+ sorted.forEach((f) => {
1294
+ updates[f.id] = { y: cy };
1295
+ cy += f.height + gap;
1296
+ });
1297
+ }
1298
+ return prev.map((f) => updates[f.id] ? { ...f, ...updates[f.id] } : f);
1299
+ });
1300
+ }, [selectedFieldIds]);
1301
+ const handleFieldDelete = useCallback4((id) => {
1302
+ const field = fields.find((f) => f.id === id);
1303
+ if (field?.locked) return;
880
1304
  setFields((prev) => prev.filter((f) => f.id !== id));
881
1305
  setSelectedFieldIds((prev) => {
882
1306
  const next = new Set(prev);
@@ -884,7 +1308,7 @@ function DesignerView({
884
1308
  return next;
885
1309
  });
886
1310
  }, []);
887
- const handleSelectField = useCallback3((id, e) => {
1311
+ const handleSelectField = useCallback4((id, e) => {
888
1312
  if (!id) {
889
1313
  setSelectedFieldIds(/* @__PURE__ */ new Set());
890
1314
  return;
@@ -918,15 +1342,15 @@ function DesignerView({
918
1342
  }
919
1343
  setRightTab("properties");
920
1344
  }, [selectedFieldIds]);
921
- const handleGroupMove = useCallback3((ids, dx, dy) => {
922
- setFields((prev) => prev.map((f) => {
923
- if (!ids.includes(f.id)) return f;
1345
+ const handleGroupMove = useCallback4((ids, dx, dy) => {
1346
+ setFieldsSilent((prev) => prev.map((f) => {
1347
+ if (!ids.includes(f.id) || f.locked) return f;
924
1348
  const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
925
1349
  const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
926
1350
  return { ...f, x: newX, y: newY };
927
1351
  }));
928
- }, []);
929
- const handleAddRole = useCallback3(() => {
1352
+ }, [setFieldsSilent]);
1353
+ const handleAddRole = useCallback4(() => {
930
1354
  const name = newRoleName.trim();
931
1355
  if (name && !signerRoles.includes(name)) {
932
1356
  setSignerRoles((prev) => [...prev, name]);
@@ -934,12 +1358,12 @@ function DesignerView({
934
1358
  setNewRoleName("");
935
1359
  setIsAddingRole(false);
936
1360
  }, [newRoleName, signerRoles]);
937
- const handleRemoveRole = useCallback3((role) => {
1361
+ const handleRemoveRole = useCallback4((role) => {
938
1362
  setSignerRoles((prev) => prev.filter((r) => r !== role));
939
1363
  setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
940
1364
  if (activeRole === role) setActiveRole(signerRoles[0]);
941
1365
  }, [signerRoles, activeRole]);
942
- const handleExport = useCallback3(() => {
1366
+ const handleExport = useCallback4(() => {
943
1367
  const template = {
944
1368
  fields,
945
1369
  signerRoles,
@@ -959,7 +1383,7 @@ function DesignerView({
959
1383
  }
960
1384
  window.parent?.postMessage({ type: "template-saved", template }, "*");
961
1385
  }, [fields, signerRoles, pdfSource, onSave]);
962
- const handlePaletteDragStart = useCallback3((e, type) => {
1386
+ const handlePaletteDragStart = useCallback4((e, type) => {
963
1387
  setDraggingFieldType(type);
964
1388
  e.dataTransfer.setData("application/exeq-field-type", type);
965
1389
  e.dataTransfer.effectAllowed = "copy";
@@ -976,14 +1400,14 @@ function DesignerView({
976
1400
  e.dataTransfer.setDragImage(ghost, 40, 16);
977
1401
  dragGhostRef.current = ghost;
978
1402
  }, [activeRole]);
979
- const handlePaletteDragEnd = useCallback3(() => {
1403
+ const handlePaletteDragEnd = useCallback4(() => {
980
1404
  setDraggingFieldType(null);
981
1405
  if (dragGhostRef.current) {
982
1406
  document.body.removeChild(dragGhostRef.current);
983
1407
  dragGhostRef.current = null;
984
1408
  }
985
1409
  }, []);
986
- const handleDropOnPage = useCallback3((page, x, y, fieldType) => {
1410
+ const handleDropOnPage = useCallback4((page, x, y, fieldType) => {
987
1411
  const field = createField(fieldType, activeRole, page, x, y, fields);
988
1412
  const sticky = lastStylesRef.current[fieldType];
989
1413
  const styledField = sticky ? { ...field, ...sticky } : field;
@@ -999,21 +1423,39 @@ function DesignerView({
999
1423
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1000
1424
  if (document.activeElement?.isContentEditable) return;
1001
1425
  e.preventDefault();
1002
- const count = selectedFieldIds.size;
1003
- if (window.confirm(`Delete ${count > 1 ? `${count} fields` : "this field"}?`)) {
1004
- setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id)));
1005
- setSelectedFieldIds(/* @__PURE__ */ new Set());
1006
- }
1426
+ setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
1427
+ setSelectedFieldIds((prev) => {
1428
+ const remaining = /* @__PURE__ */ new Set();
1429
+ fields.forEach((f) => {
1430
+ if (selectedFieldIds.has(f.id) && f.locked) remaining.add(f.id);
1431
+ });
1432
+ return remaining;
1433
+ });
1007
1434
  };
1008
1435
  window.addEventListener("keydown", handleKeyDown);
1009
1436
  return () => window.removeEventListener("keydown", handleKeyDown);
1010
- }, [selectedFieldIds]);
1437
+ }, [selectedFieldIds, fields]);
1011
1438
  useEffect2(() => {
1012
1439
  const handleKeyDown = (e) => {
1013
1440
  const tag = (document.activeElement?.tagName || "").toLowerCase();
1014
1441
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1015
1442
  if (document.activeElement?.isContentEditable) return;
1016
1443
  const isMod = e.metaKey || e.ctrlKey;
1444
+ if (isMod && e.key === "a") {
1445
+ e.preventDefault();
1446
+ setSelectedFieldIds(new Set(fields.map((f) => f.id)));
1447
+ return;
1448
+ }
1449
+ if (isMod && e.key === "z" && !e.shiftKey) {
1450
+ e.preventDefault();
1451
+ undoFields();
1452
+ return;
1453
+ }
1454
+ if (isMod && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1455
+ e.preventDefault();
1456
+ redoFields();
1457
+ return;
1458
+ }
1017
1459
  if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
1018
1460
  const selected = fields.filter((f) => selectedFieldIds.has(f.id));
1019
1461
  if (selected.length > 0) {
@@ -1040,7 +1482,75 @@ function DesignerView({
1040
1482
  window.addEventListener("keydown", handleKeyDown);
1041
1483
  return () => window.removeEventListener("keydown", handleKeyDown);
1042
1484
  }, [selectedFieldIds, fields, clipboardFields]);
1043
- const handleResizeStart = useCallback3((e) => {
1485
+ useEffect2(() => {
1486
+ const handleKeyDown = (e) => {
1487
+ const isMod = e.metaKey || e.ctrlKey;
1488
+ if (isMod && (e.key === "=" || e.key === "+")) {
1489
+ e.preventDefault();
1490
+ setZoom((z) => Math.min(3, z + 0.25));
1491
+ }
1492
+ if (isMod && e.key === "-") {
1493
+ e.preventDefault();
1494
+ setZoom((z) => Math.max(0.75, z - 0.25));
1495
+ }
1496
+ if (isMod && e.key === "0") {
1497
+ e.preventDefault();
1498
+ setZoom(1);
1499
+ }
1500
+ if (e.key === " ") {
1501
+ const tag = (document.activeElement?.tagName || "").toLowerCase();
1502
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
1503
+ e.preventDefault();
1504
+ e.stopPropagation();
1505
+ setIsPanning(true);
1506
+ }
1507
+ };
1508
+ const handleKeyUp = (e) => {
1509
+ if (e.key === " ") {
1510
+ e.preventDefault();
1511
+ setIsPanning(false);
1512
+ panRef.current = null;
1513
+ }
1514
+ };
1515
+ window.addEventListener("keydown", handleKeyDown, { capture: true });
1516
+ window.addEventListener("keyup", handleKeyUp, { capture: true });
1517
+ return () => {
1518
+ window.removeEventListener("keydown", handleKeyDown, { capture: true });
1519
+ window.removeEventListener("keyup", handleKeyUp, { capture: true });
1520
+ };
1521
+ }, []);
1522
+ useEffect2(() => {
1523
+ const el = pdfAreaRef.current;
1524
+ if (!el) return;
1525
+ const handleWheel = (e) => {
1526
+ if (e.ctrlKey || e.metaKey) {
1527
+ e.preventDefault();
1528
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
1529
+ setZoom((z) => Math.max(0.75, Math.min(3, z + delta)));
1530
+ }
1531
+ };
1532
+ el.addEventListener("wheel", handleWheel, { passive: false });
1533
+ return () => el.removeEventListener("wheel", handleWheel);
1534
+ }, [pages.length]);
1535
+ const handlePanMouseDown = useCallback4((e) => {
1536
+ if (!isPanning || !pdfAreaRef.current) return;
1537
+ e.preventDefault();
1538
+ panRef.current = {
1539
+ startX: e.clientX,
1540
+ startY: e.clientY,
1541
+ scrollLeft: pdfAreaRef.current.scrollLeft,
1542
+ scrollTop: pdfAreaRef.current.scrollTop
1543
+ };
1544
+ }, [isPanning]);
1545
+ const handlePanMouseMove = useCallback4((e) => {
1546
+ if (!panRef.current || !pdfAreaRef.current) return;
1547
+ pdfAreaRef.current.scrollLeft = panRef.current.scrollLeft - (e.clientX - panRef.current.startX);
1548
+ pdfAreaRef.current.scrollTop = panRef.current.scrollTop - (e.clientY - panRef.current.startY);
1549
+ }, []);
1550
+ const handlePanMouseUp = useCallback4(() => {
1551
+ panRef.current = null;
1552
+ }, []);
1553
+ const handleResizeStart = useCallback4((e) => {
1044
1554
  e.preventDefault();
1045
1555
  resizingRef.current = true;
1046
1556
  const startX = e.clientX;
@@ -1065,7 +1575,7 @@ function DesignerView({
1065
1575
  return a.x - b.x;
1066
1576
  });
1067
1577
  const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
1068
- const renderFieldContent = useCallback3((field) => {
1578
+ const renderFieldContent = useCallback4((field) => {
1069
1579
  if (field.type === "blackout" || field.type === "whiteout") {
1070
1580
  return null;
1071
1581
  }
@@ -1091,7 +1601,7 @@ function DesignerView({
1091
1601
  }
1092
1602
  );
1093
1603
  }, [fields]);
1094
- const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1604
+ const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1095
1605
  /* @__PURE__ */ jsxs4("label", { className: "header-btn header-btn-outline", children: [
1096
1606
  "Change PDF",
1097
1607
  /* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
@@ -1169,39 +1679,91 @@ function DesignerView({
1169
1679
  type
1170
1680
  ))
1171
1681
  ] }),
1172
- /* @__PURE__ */ jsxs4("div", { className: "designer-pdf-area", children: [
1173
- loading && /* @__PURE__ */ jsx4("div", { className: "loading-indicator", children: "Loading PDF..." }),
1174
- !pages.length && !loading && /* @__PURE__ */ jsxs4("div", { className: "empty-state", children: [
1175
- /* @__PURE__ */ jsx4("h2", { children: "Open a PDF to get started" }),
1176
- /* @__PURE__ */ jsx4("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
1177
- /* @__PURE__ */ jsxs4("label", { className: "upload-btn upload-btn-large", children: [
1178
- "Select PDF",
1179
- /* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
1180
- ] })
1181
- ] }),
1182
- pages.length > 0 && /* @__PURE__ */ jsx4(
1183
- PdfViewer,
1184
- {
1185
- pages,
1186
- fields,
1187
- selectedFieldIds,
1188
- onSelectField: handleSelectField,
1189
- onFieldMove: handleFieldMove,
1190
- onFieldResize: handleFieldResize,
1191
- onGroupMove: handleGroupMove,
1192
- onPageClick: handlePageClick,
1193
- onDropField: handleDropOnPage,
1194
- mode: "designer",
1195
- renderFieldContent
1196
- }
1197
- )
1198
- ] }),
1199
- pages.length > 0 && /* @__PURE__ */ jsxs4(Fragment, { children: [
1682
+ /* @__PURE__ */ jsxs4(
1683
+ "div",
1684
+ {
1685
+ className: `designer-pdf-area ${isPanning ? "panning" : ""}`,
1686
+ ref: pdfAreaRef,
1687
+ onMouseDown: isPanning ? handlePanMouseDown : void 0,
1688
+ onMouseMove: isPanning ? handlePanMouseMove : void 0,
1689
+ onMouseUp: isPanning ? handlePanMouseUp : void 0,
1690
+ onMouseLeave: isPanning ? handlePanMouseUp : void 0,
1691
+ children: [
1692
+ loading && /* @__PURE__ */ jsx4("div", { className: "loading-indicator", children: "Loading PDF..." }),
1693
+ !pages.length && !loading && /* @__PURE__ */ jsxs4("div", { className: "empty-state", children: [
1694
+ /* @__PURE__ */ jsx4("h2", { children: "Open a PDF to get started" }),
1695
+ /* @__PURE__ */ jsx4("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
1696
+ /* @__PURE__ */ jsxs4("label", { className: "upload-btn upload-btn-large", children: [
1697
+ "Select PDF",
1698
+ /* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
1699
+ ] })
1700
+ ] }),
1701
+ pages.length > 0 && /* @__PURE__ */ jsx4(
1702
+ PdfViewer,
1703
+ {
1704
+ pages,
1705
+ fields,
1706
+ selectedFieldIds,
1707
+ onSelectField: handleSelectField,
1708
+ onFieldMove: handleFieldMove,
1709
+ onFieldResize: handleFieldResize,
1710
+ onGroupMove: handleGroupMove,
1711
+ onMoveEnd: snapshotFields,
1712
+ onPageClick: handlePageClick,
1713
+ onDropField: handleDropOnPage,
1714
+ onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
1715
+ mode: "designer",
1716
+ renderFieldContent,
1717
+ zoom
1718
+ }
1719
+ )
1720
+ ]
1721
+ }
1722
+ ),
1723
+ pages.length > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1200
1724
  /* @__PURE__ */ jsx4("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
1201
1725
  /* @__PURE__ */ jsxs4("div", { className: "designer-panel", style: { width: panelWidth }, children: [
1202
- /* @__PURE__ */ jsxs4("div", { className: "signer-role-indicator", children: [
1203
- /* @__PURE__ */ jsx4("span", { className: "signer-role-indicator-label", children: "Editing as" }),
1204
- /* @__PURE__ */ jsx4("strong", { children: activeRole })
1726
+ /* @__PURE__ */ jsxs4("div", { className: "designer-panel-header", children: [
1727
+ /* @__PURE__ */ jsxs4("div", { className: "signer-role-indicator", children: [
1728
+ /* @__PURE__ */ jsx4("span", { className: "signer-role-indicator-label", children: "Editing as" }),
1729
+ /* @__PURE__ */ jsx4("strong", { children: activeRole })
1730
+ ] }),
1731
+ /* @__PURE__ */ jsxs4("div", { className: "zoom-slider", children: [
1732
+ /* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.max(0.75, z - 0.25)), disabled: zoom <= 0.75, children: "\u2212" }),
1733
+ /* @__PURE__ */ jsx4(
1734
+ "input",
1735
+ {
1736
+ type: "range",
1737
+ min: "75",
1738
+ max: "300",
1739
+ step: "25",
1740
+ value: Math.round(zoom * 100),
1741
+ onChange: (e) => setZoom(Number(e.target.value) / 100)
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.min(3, z + 0.25)), disabled: zoom >= 3, children: "+" }),
1745
+ /* @__PURE__ */ jsx4("div", { className: "zoom-dropdown-wrapper", children: /* @__PURE__ */ jsxs4(
1746
+ "select",
1747
+ {
1748
+ className: "zoom-dropdown",
1749
+ value: Math.round(zoom * 100),
1750
+ onChange: (e) => setZoom(Number(e.target.value) / 100),
1751
+ children: [
1752
+ /* @__PURE__ */ jsx4("option", { value: "75", children: "75%" }),
1753
+ /* @__PURE__ */ jsx4("option", { value: "100", children: "100%" }),
1754
+ /* @__PURE__ */ jsx4("option", { value: "125", children: "125%" }),
1755
+ /* @__PURE__ */ jsx4("option", { value: "150", children: "150%" }),
1756
+ /* @__PURE__ */ jsx4("option", { value: "200", children: "200%" }),
1757
+ /* @__PURE__ */ jsx4("option", { value: "250", children: "250%" }),
1758
+ /* @__PURE__ */ jsx4("option", { value: "300", children: "300%" }),
1759
+ ![75, 100, 125, 150, 200, 250, 300].includes(Math.round(zoom * 100)) && /* @__PURE__ */ jsxs4("option", { value: Math.round(zoom * 100), children: [
1760
+ Math.round(zoom * 100),
1761
+ "%"
1762
+ ] })
1763
+ ]
1764
+ }
1765
+ ) })
1766
+ ] })
1205
1767
  ] }),
1206
1768
  /* @__PURE__ */ jsxs4("div", { className: "panel-tabs", children: [
1207
1769
  /* @__PURE__ */ jsx4(
@@ -1225,82 +1787,163 @@ function DesignerView({
1225
1787
  )
1226
1788
  ] }),
1227
1789
  /* @__PURE__ */ jsxs4("div", { className: "panel-tab-content", children: [
1228
- rightTab === "properties" && /* @__PURE__ */ jsx4(Fragment, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ jsxs4("div", { className: "panel-empty", children: [
1229
- /* @__PURE__ */ jsxs4("strong", { children: [
1230
- selectedFieldIds.size,
1231
- " fields selected"
1232
- ] }),
1233
- /* @__PURE__ */ jsx4("p", { children: "Drag to move as a group. Press Delete to remove all. Cmd/Ctrl+C to copy." })
1234
- ] }) : selectedField ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1235
- /* @__PURE__ */ jsx4(
1236
- FieldPropertyPanel,
1237
- {
1238
- field: selectedField,
1239
- signerRoles,
1240
- onUpdate: handleFieldUpdate,
1241
- onDelete: handleFieldDelete
1790
+ rightTab === "properties" && /* @__PURE__ */ jsx4(Fragment2, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ jsx4(
1791
+ BulkPropertyPanel,
1792
+ {
1793
+ fields: fields.filter((f) => selectedFieldIds.has(f.id)),
1794
+ signerRoles,
1795
+ onUpdate: handleBulkUpdate,
1796
+ onAlign: handleAlign,
1797
+ onDelete: () => {
1798
+ setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
1799
+ setSelectedFieldIds((prev) => {
1800
+ const remaining = /* @__PURE__ */ new Set();
1801
+ fields.forEach((f) => {
1802
+ if (prev.has(f.id) && f.locked) remaining.add(f.id);
1803
+ });
1804
+ return remaining;
1805
+ });
1242
1806
  }
1243
- ),
1244
- selectedField.type !== "blackout" && selectedField.type !== "whiteout" && /* @__PURE__ */ jsxs4("div", { className: "prefill-section", children: [
1245
- /* @__PURE__ */ jsx4("h4", { children: "Pre-fill Value" }),
1246
- selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ jsx4(
1247
- SignatureCanvas,
1248
- {
1249
- width: panelWidth - 40,
1250
- height: selectedField.type === "initials" ? 100 : 150,
1251
- onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
1252
- initialValue: selectedField.value,
1253
- inkColor: selectedField.inkColor
1254
- }
1255
- ) : selectedField.type === "checkbox" ? /* @__PURE__ */ jsxs4("label", { className: "panel-checkbox-label", children: [
1256
- /* @__PURE__ */ jsx4(
1257
- "input",
1807
+ }
1808
+ ) : selectedField ? /* @__PURE__ */ jsx4(Fragment2, { children: /* @__PURE__ */ jsx4(
1809
+ FieldPropertyPanel,
1810
+ {
1811
+ field: selectedField,
1812
+ signerRoles,
1813
+ onUpdate: handleFieldUpdate,
1814
+ onDelete: handleFieldDelete,
1815
+ pageSize: pages[selectedField.page] ? { width: pages[selectedField.page].pdfWidth, height: pages[selectedField.page].pdfHeight } : void 0,
1816
+ prefillContent: selectedField.type !== "blackout" && selectedField.type !== "whiteout" ? /* @__PURE__ */ jsxs4("div", { className: "prefill-section", children: [
1817
+ /* @__PURE__ */ jsx4("label", { children: "Pre-fill Value" }),
1818
+ selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ jsx4(
1819
+ SignatureCanvas,
1258
1820
  {
1259
- type: "checkbox",
1260
- checked: selectedField.value === "true",
1261
- onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
1821
+ width: panelWidth - 40,
1822
+ height: selectedField.type === "initials" ? 100 : 150,
1823
+ onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
1824
+ initialValue: selectedField.value,
1825
+ inkColor: selectedField.inkColor
1262
1826
  }
1263
- ),
1264
- "Checked"
1265
- ] }) : /* @__PURE__ */ jsx4(
1266
- "input",
1267
- {
1268
- type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
1269
- value: selectedField.value,
1270
- onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
1271
- placeholder: `Pre-fill ${selectedField.label}`,
1272
- className: "prefill-input",
1273
- style: { color: selectedField.inkColor || "#000000" }
1274
- }
1275
- )
1276
- ] })
1277
- ] }) : /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
1278
- rightTab === "fields" && /* @__PURE__ */ jsx4(Fragment, { children: fields.length === 0 ? /* @__PURE__ */ jsx4("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__ */ jsx4("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ jsxs4(
1279
- "div",
1280
- {
1281
- className: `field-list-item ${selectedFieldIds.has(f.id) ? "active" : ""}`,
1282
- onClick: () => {
1283
- setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
1284
- setRightTab("properties");
1285
- },
1286
- children: [
1287
- /* @__PURE__ */ jsx4(
1288
- "span",
1827
+ ) : selectedField.type === "checkbox" ? /* @__PURE__ */ jsxs4("label", { className: "panel-checkbox-label", children: [
1828
+ /* @__PURE__ */ jsx4(
1829
+ "input",
1830
+ {
1831
+ type: "checkbox",
1832
+ checked: selectedField.value === "true",
1833
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
1834
+ }
1835
+ ),
1836
+ "Checked"
1837
+ ] }) : /* @__PURE__ */ jsx4(
1838
+ "input",
1289
1839
  {
1290
- className: "field-list-dot",
1291
- style: { backgroundColor: getSignerColor(f.assignee) }
1840
+ type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
1841
+ value: selectedField.value,
1842
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
1843
+ placeholder: `Pre-fill ${selectedField.label}`,
1844
+ className: "prefill-input",
1845
+ style: { color: selectedField.inkColor || "#000000" }
1292
1846
  }
1293
- ),
1294
- /* @__PURE__ */ jsx4("span", { className: "field-list-name", children: f.label }),
1295
- /* @__PURE__ */ jsxs4("span", { className: "field-list-page", children: [
1296
- "p",
1297
- f.page + 1
1298
- ] }),
1299
- f.required && /* @__PURE__ */ jsx4("span", { className: "field-list-required", children: "*" })
1300
- ]
1301
- },
1302
- f.id
1303
- )) }) })
1847
+ )
1848
+ ] }) : void 0
1849
+ }
1850
+ ) }) : /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
1851
+ rightTab === "fields" && /* @__PURE__ */ jsx4(Fragment2, { children: fields.length === 0 ? /* @__PURE__ */ jsx4("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__ */ jsx4("div", { className: "field-list", children: sortedFields.map((f, idx) => {
1852
+ const isSel = selectedFieldIds.has(f.id);
1853
+ const isSingleSel = isSel && selectedFieldIds.size === 1;
1854
+ const isTextLike = f.type === "text" || f.type === "dropdown" || f.type === "signed-date";
1855
+ return /* @__PURE__ */ jsxs4(
1856
+ "div",
1857
+ {
1858
+ className: `field-list-item ${isSel ? "active" : ""}`,
1859
+ onClick: (e) => {
1860
+ if (e.target.tagName === "INPUT") return;
1861
+ if (e.metaKey || e.ctrlKey) {
1862
+ setSelectedFieldIds((prev) => {
1863
+ const next = new Set(prev);
1864
+ if (next.has(f.id)) next.delete(f.id);
1865
+ else next.add(f.id);
1866
+ return next;
1867
+ });
1868
+ } else if (e.shiftKey && selectedFieldIds.size > 0) {
1869
+ const ids = sortedFields.map((sf) => sf.id);
1870
+ const lastId = Array.from(selectedFieldIds).pop();
1871
+ const a = ids.indexOf(lastId);
1872
+ const b = ids.indexOf(f.id);
1873
+ if (a >= 0 && b >= 0) {
1874
+ const start = Math.min(a, b);
1875
+ const end = Math.max(a, b);
1876
+ setSelectedFieldIds((prev) => {
1877
+ const next = new Set(prev);
1878
+ ids.slice(start, end + 1).forEach((id) => next.add(id));
1879
+ return next;
1880
+ });
1881
+ }
1882
+ } else {
1883
+ setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
1884
+ }
1885
+ },
1886
+ children: [
1887
+ /* @__PURE__ */ jsx4(
1888
+ "span",
1889
+ {
1890
+ className: "field-list-dot",
1891
+ style: { backgroundColor: getSignerColor(f.assignee) }
1892
+ }
1893
+ ),
1894
+ /* @__PURE__ */ jsx4("span", { className: "field-list-name", children: f.label }),
1895
+ f.locked && /* @__PURE__ */ jsx4("span", { className: "field-list-lock", title: "Locked", children: "\u{1F512}" }),
1896
+ /* @__PURE__ */ jsxs4("span", { className: "field-list-page", children: [
1897
+ "p",
1898
+ f.page + 1
1899
+ ] }),
1900
+ f.required && /* @__PURE__ */ jsx4("span", { className: "field-list-required", children: "*" }),
1901
+ isSingleSel && isTextLike && /* @__PURE__ */ jsx4(
1902
+ "input",
1903
+ {
1904
+ className: "field-list-input",
1905
+ type: "text",
1906
+ value: f.value,
1907
+ placeholder: f.placeholder,
1908
+ maxLength: f.maxLength || void 0,
1909
+ autoFocus: true,
1910
+ onChange: (e) => handleFieldUpdate(f.id, { value: e.target.value }),
1911
+ onKeyDown: (e) => {
1912
+ if (e.key === "Tab") {
1913
+ e.preventDefault();
1914
+ const dir = e.shiftKey ? -1 : 1;
1915
+ const nextIdx = idx + dir;
1916
+ if (nextIdx >= 0 && nextIdx < sortedFields.length) {
1917
+ setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
1918
+ }
1919
+ }
1920
+ },
1921
+ onClick: (e) => e.stopPropagation()
1922
+ }
1923
+ ),
1924
+ isSingleSel && f.type === "checkbox" && /* @__PURE__ */ jsx4("label", { className: "field-list-checkbox", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx4(
1925
+ "input",
1926
+ {
1927
+ type: "checkbox",
1928
+ checked: f.value === "true",
1929
+ onChange: (e) => handleFieldUpdate(f.id, { value: e.target.checked ? "true" : "" }),
1930
+ onKeyDown: (e) => {
1931
+ if (e.key === "Tab") {
1932
+ e.preventDefault();
1933
+ const dir = e.shiftKey ? -1 : 1;
1934
+ const nextIdx = idx + dir;
1935
+ if (nextIdx >= 0 && nextIdx < sortedFields.length) {
1936
+ setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
1937
+ }
1938
+ }
1939
+ }
1940
+ }
1941
+ ) })
1942
+ ]
1943
+ },
1944
+ f.id
1945
+ );
1946
+ }) }) })
1304
1947
  ] }),
1305
1948
  /* @__PURE__ */ jsxs4("div", { className: "powered-by", children: [
1306
1949
  "Powered by ",
@@ -1313,7 +1956,7 @@ function DesignerView({
1313
1956
  }
1314
1957
 
1315
1958
  // src/components/pdf-builder/SignerView.tsx
1316
- import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
1959
+ import { useState as useState7, useCallback as useCallback5, useEffect as useEffect3, useRef as useRef5 } from "react";
1317
1960
 
1318
1961
  // src/utils/pdfFiller.ts
1319
1962
  import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
@@ -1450,7 +2093,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
1450
2093
  }
1451
2094
 
1452
2095
  // src/components/pdf-builder/FieldNavigator.tsx
1453
- import { useState as useState4 } from "react";
2096
+ import { useState as useState6 } from "react";
1454
2097
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1455
2098
  function isFieldFilled(f) {
1456
2099
  if (!f.required) return true;
@@ -1465,7 +2108,7 @@ function FieldNavigator({
1465
2108
  onComplete,
1466
2109
  completeLabel = "Complete"
1467
2110
  }) {
1468
- const [showIncomplete, setShowIncomplete] = useState4(false);
2111
+ const [showIncomplete, setShowIncomplete] = useState6(false);
1469
2112
  const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
1470
2113
  const hasPrev = currentIndex > 0;
1471
2114
  const hasNext = currentIndex < fields.length - 1;
@@ -1630,7 +2273,7 @@ function resolveAllFormulas(fields, customTransforms) {
1630
2273
  }
1631
2274
 
1632
2275
  // src/components/pdf-builder/SignerView.tsx
1633
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2276
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1634
2277
  function SignerView({
1635
2278
  apiKey,
1636
2279
  initialPdfUrl,
@@ -1641,7 +2284,12 @@ function SignerView({
1641
2284
  initialValues,
1642
2285
  submitLabel,
1643
2286
  signerOrder: signerOrderProp,
1644
- transforms
2287
+ transforms,
2288
+ onChange,
2289
+ onSignerComplete,
2290
+ includeAuditTrail,
2291
+ exportFormat,
2292
+ onExport
1645
2293
  } = {}) {
1646
2294
  if (!isValidApiKey(apiKey)) {
1647
2295
  return /* @__PURE__ */ jsx6("div", { className: "signer-layout", children: /* @__PURE__ */ jsxs6("div", { className: "empty-state", children: [
@@ -1653,23 +2301,24 @@ function SignerView({
1653
2301
  ] })
1654
2302
  ] }) });
1655
2303
  }
1656
- const [pages, setPages] = useState5([]);
1657
- const [fields, setFields] = useState5([]);
1658
- const [selectedFieldId, setSelectedFieldId] = useState5(null);
1659
- const [signerRoles, setSignerRoles] = useState5([]);
1660
- const [currentSignerIndex, setCurrentSignerIndex] = useState5(() => {
2304
+ const [pages, setPages] = useState7([]);
2305
+ const [fields, setFields] = useState7([]);
2306
+ const [selectedFieldId, setSelectedFieldId] = useState7(null);
2307
+ const [signerRoles, setSignerRoles] = useState7([]);
2308
+ const [currentSignerIndex, setCurrentSignerIndex] = useState7(() => {
1661
2309
  if (initialSigner && signerOrderProp) {
1662
2310
  const idx = signerOrderProp.indexOf(initialSigner);
1663
2311
  return idx >= 0 ? idx : 0;
1664
2312
  }
1665
2313
  return 0;
1666
2314
  });
1667
- const initializedRef = useRef4(false);
1668
- const [loading, setLoading] = useState5(false);
1669
- const [submitting, setSubmitting] = useState5(false);
1670
- const [pdfSource, setPdfSource] = useState5(null);
1671
- const [callbackUrl, setCallbackUrl] = useState5(initialCallbackUrl || "");
1672
- const containerRef = useRef4(null);
2315
+ const initializedRef = useRef5(false);
2316
+ const auditLogRef = useRef5([]);
2317
+ const [loading, setLoading] = useState7(false);
2318
+ const [submitting, setSubmitting] = useState7(false);
2319
+ const [pdfSource, setPdfSource] = useState7(null);
2320
+ const [callbackUrl, setCallbackUrl] = useState7(initialCallbackUrl || "");
2321
+ const containerRef = useRef5(null);
1673
2322
  const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
1674
2323
  const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
1675
2324
  const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
@@ -1759,7 +2408,7 @@ function SignerView({
1759
2408
  window.addEventListener("message", handleMessage);
1760
2409
  return () => window.removeEventListener("message", handleMessage);
1761
2410
  }, [initialTemplate, initialPdfUrl]);
1762
- const loadPdf = useCallback4(async (source) => {
2411
+ const loadPdf = useCallback5(async (source) => {
1763
2412
  setLoading(true);
1764
2413
  try {
1765
2414
  const rendered = await renderPdfPages(source);
@@ -1779,13 +2428,22 @@ function SignerView({
1779
2428
  });
1780
2429
  const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
1781
2430
  const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
1782
- const handleFieldUpdate = useCallback4((id, value) => {
2431
+ const handleFieldUpdate = useCallback5((id, value) => {
1783
2432
  setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
1784
2433
  }, []);
1785
- const handleFieldPropertyUpdate = useCallback4((id, updates) => {
2434
+ const handleFieldPropertyUpdate = useCallback5((id, updates) => {
1786
2435
  setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
1787
2436
  }, []);
1788
- const handleNavigate = useCallback4((fieldId) => {
2437
+ const fieldValuesKey = fields.map((f) => `${f.id}:${f.value}`).join("|");
2438
+ useEffect3(() => {
2439
+ if (!onChange) return;
2440
+ const values = {};
2441
+ fields.forEach((f) => {
2442
+ if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
2443
+ });
2444
+ onChange(values);
2445
+ }, [fieldValuesKey]);
2446
+ const handleNavigate = useCallback5((fieldId) => {
1789
2447
  setSelectedFieldId(fieldId);
1790
2448
  const field = fields.find((f) => f.id === fieldId);
1791
2449
  if (field && containerRef.current) {
@@ -1798,10 +2456,23 @@ function SignerView({
1798
2456
  const allRequiredFilled = editableFields.every((f) => {
1799
2457
  if (!f.required) return true;
1800
2458
  if (f.type === "checkbox") return true;
1801
- return !!f.value;
2459
+ if (!f.value) return false;
2460
+ if (f.minLength && f.value.length < f.minLength) return false;
2461
+ return true;
1802
2462
  });
1803
- const handleAdvanceOrSubmit = useCallback4(async () => {
2463
+ const getFieldValues = useCallback5(() => {
2464
+ const values = {};
2465
+ fields.forEach((f) => {
2466
+ if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
2467
+ });
2468
+ return values;
2469
+ }, [fields]);
2470
+ const handleAdvanceOrSubmit = useCallback5(async () => {
1804
2471
  if (!allRequiredFilled) return;
2472
+ auditLogRef.current.push({ signer, completedAt: (/* @__PURE__ */ new Date()).toISOString() });
2473
+ if (onSignerComplete) {
2474
+ onSignerComplete(signer, getFieldValues());
2475
+ }
1805
2476
  if (!isLastSigner) {
1806
2477
  setCurrentSignerIndex((prev) => prev + 1);
1807
2478
  setSelectedFieldId(null);
@@ -1815,8 +2486,40 @@ function SignerView({
1815
2486
  setSubmitting(true);
1816
2487
  try {
1817
2488
  const finalFields = resolveAllFormulas(fields, transforms);
1818
- const pdfBytes = await generateFilledPdf(pdfSource, finalFields);
2489
+ let pdfBytes;
2490
+ if (includeAuditTrail) {
2491
+ const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
2492
+ const basePdf = await generateFilledPdf(pdfSource, finalFields);
2493
+ const pdfDoc = await PDFDocument2.load(basePdf);
2494
+ const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
2495
+ const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
2496
+ const auditPage = pdfDoc.addPage([612, 792]);
2497
+ let cy = 742;
2498
+ auditPage.drawText("Signing Audit Trail", { x: 50, y: cy, size: 18, font: boldFont, color: rgb2(0, 0, 0) });
2499
+ cy -= 30;
2500
+ auditPage.drawText(`Document completed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, { x: 50, y: cy, size: 10, font, color: rgb2(0.4, 0.4, 0.4) });
2501
+ cy -= 30;
2502
+ for (const entry of auditLogRef.current) {
2503
+ auditPage.drawText(`${entry.signer}`, { x: 50, y: cy, size: 12, font: boldFont, color: rgb2(0, 0, 0) });
2504
+ auditPage.drawText(`Completed: ${new Date(entry.completedAt).toLocaleString()}`, { x: 200, y: cy, size: 10, font, color: rgb2(0.3, 0.3, 0.3) });
2505
+ cy -= 22;
2506
+ }
2507
+ pdfBytes = await pdfDoc.save();
2508
+ } else {
2509
+ pdfBytes = await generateFilledPdf(pdfSource, finalFields);
2510
+ }
1819
2511
  const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
2512
+ if (onExport && exportFormat) {
2513
+ const values = getFieldValues();
2514
+ if (exportFormat === "json") {
2515
+ onExport(JSON.stringify(values, null, 2), "json");
2516
+ } else {
2517
+ const headers = Object.keys(values);
2518
+ const row = headers.map((h) => `"${(values[h] || "").replace(/"/g, '""')}"`);
2519
+ onExport(`${headers.join(",")}
2520
+ ${row.join(",")}`, "csv");
2521
+ }
2522
+ }
1820
2523
  if (callbackUrl) {
1821
2524
  await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
1822
2525
  }
@@ -1832,8 +2535,8 @@ function SignerView({
1832
2535
  } finally {
1833
2536
  setSubmitting(false);
1834
2537
  }
1835
- }, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner]);
1836
- const renderFieldContent = useCallback4((field) => {
2538
+ }, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner, signer, onSignerComplete, includeAuditTrail, exportFormat, onExport, getFieldValues, transforms]);
2539
+ const renderFieldContent = useCallback5((field) => {
1837
2540
  if (field.type === "blackout" || field.type === "whiteout") {
1838
2541
  return null;
1839
2542
  }
@@ -1970,7 +2673,7 @@ function SignerView({
1970
2673
  initialValue: selectedField.value
1971
2674
  }
1972
2675
  ),
1973
- (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment2, { children: [
2676
+ (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1974
2677
  selectedField.type === "dropdown" || selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ jsxs6(
1975
2678
  "select",
1976
2679
  {
@@ -2051,7 +2754,7 @@ function SignerView({
2051
2754
  }
2052
2755
 
2053
2756
  // src/components/pdf-builder/SignerRoleSelector.tsx
2054
- import { useState as useState6 } from "react";
2757
+ import { useState as useState8 } from "react";
2055
2758
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2056
2759
  function SignerRoleSelector({
2057
2760
  roles,
@@ -2060,8 +2763,8 @@ function SignerRoleSelector({
2060
2763
  onAddRole,
2061
2764
  onRemoveRole
2062
2765
  }) {
2063
- const [isAdding, setIsAdding] = useState6(false);
2064
- const [newRoleName, setNewRoleName] = useState6("");
2766
+ const [isAdding, setIsAdding] = useState8(false);
2767
+ const [newRoleName, setNewRoleName] = useState8("");
2065
2768
  const handleAdd = () => {
2066
2769
  if (newRoleName.trim()) {
2067
2770
  onAddRole(newRoleName.trim());