@unlev/exeq 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,70 @@ function PdfViewer({
162
165
  onPageClick,
163
166
  onDropField,
164
167
  onGroupMove,
168
+ onMoveStart,
169
+ onMoveEnd,
165
170
  mode,
166
171
  currentSigner,
167
- renderFieldContent
172
+ renderFieldContent,
173
+ zoom = 1,
174
+ onMarqueeSelect
168
175
  }) {
169
176
  const containerRef = useRef(null);
170
- const handlePageClick = useCallback((e, pageIndex) => {
177
+ const [guides, setGuides] = useState([]);
178
+ const [marquee, setMarquee] = useState(null);
179
+ const marqueeRef = useRef(null);
180
+ const handlePageMouseDown = useCallback((e, pageIndex) => {
171
181
  const target = e.target;
172
182
  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]);
183
+ if (mode !== "designer") return;
184
+ e.preventDefault();
185
+ const rect = e.currentTarget.getBoundingClientRect();
186
+ const x = (e.clientX - rect.left) / rect.width * 100;
187
+ const y = (e.clientY - rect.top) / rect.height * 100;
188
+ marqueeRef.current = { startX: x, startY: y, page: pageIndex, didDrag: false };
189
+ const handleMouseMove = (ev) => {
190
+ if (!marqueeRef.current) return;
191
+ const mx = (ev.clientX - rect.left) / rect.width * 100;
192
+ const my = (ev.clientY - rect.top) / rect.height * 100;
193
+ const dx = Math.abs(mx - marqueeRef.current.startX);
194
+ const dy = Math.abs(my - marqueeRef.current.startY);
195
+ if (dx > 1 || dy > 1) {
196
+ marqueeRef.current.didDrag = true;
197
+ setMarquee({
198
+ page: pageIndex,
199
+ x1: Math.min(marqueeRef.current.startX, mx),
200
+ y1: Math.min(marqueeRef.current.startY, my),
201
+ x2: Math.max(marqueeRef.current.startX, mx),
202
+ y2: Math.max(marqueeRef.current.startY, my)
203
+ });
204
+ }
205
+ };
206
+ const handleMouseUp = (ev) => {
207
+ window.removeEventListener("mousemove", handleMouseMove);
208
+ window.removeEventListener("mouseup", handleMouseUp);
209
+ if (marqueeRef.current?.didDrag && onMarqueeSelect) {
210
+ const mx1 = Math.min(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
211
+ const my1 = Math.min(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
212
+ const mx2 = Math.max(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
213
+ const my2 = Math.max(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
214
+ const pageFields = fields.filter((f) => f.page === pageIndex);
215
+ const hits = pageFields.filter((f) => {
216
+ return f.x < mx2 && f.x + f.width > mx1 && f.y < my2 && f.y + f.height > my1;
217
+ }).map((f) => f.id);
218
+ onMarqueeSelect(hits);
219
+ } else if (!marqueeRef.current?.didDrag) {
220
+ if (onPageClick) {
221
+ onPageClick(pageIndex, x, y);
222
+ } else {
223
+ onSelectField(null);
224
+ }
225
+ }
226
+ marqueeRef.current = null;
227
+ setMarquee(null);
228
+ };
229
+ window.addEventListener("mousemove", handleMouseMove);
230
+ window.addEventListener("mouseup", handleMouseUp);
231
+ }, [mode, fields, onPageClick, onSelectField, onMarqueeSelect]);
182
232
  const handleDragOver = useCallback((e) => {
183
233
  if (e.dataTransfer.types.includes("application/exeq-field-type")) {
184
234
  e.preventDefault();
@@ -194,14 +244,14 @@ function PdfViewer({
194
244
  const y = (e.clientY - rect.top) / rect.height * 100;
195
245
  onDropField(pageIndex, x, y, fieldType);
196
246
  }, [onDropField]);
197
- return /* @__PURE__ */ jsx("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
247
+ 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
248
  const pageFields = fields.filter((f) => f.page === pageIndex);
199
249
  return /* @__PURE__ */ jsxs(
200
250
  "div",
201
251
  {
202
252
  className: "pdf-page",
203
253
  style: { aspectRatio: `${page.width} / ${page.height}` },
204
- onClick: (e) => handlePageClick(e, pageIndex),
254
+ onMouseDown: (e) => handlePageMouseDown(e, pageIndex),
205
255
  onDragOver: handleDragOver,
206
256
  onDrop: (e) => handleDrop(e, pageIndex),
207
257
  "data-page": pageIndex,
@@ -225,18 +275,87 @@ function PdfViewer({
225
275
  onMove: onFieldMove,
226
276
  onResize: onFieldResize,
227
277
  onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
278
+ onMoveStart,
279
+ onMoveEnd,
280
+ otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
281
+ setGuides: mode === "designer" ? setGuides : void 0,
282
+ pageIndex,
228
283
  selectedIds: selectedFieldIds,
229
284
  mode,
230
285
  currentSigner,
231
286
  renderContent: renderFieldContent
232
287
  },
233
288
  field.id
234
- ))
289
+ )),
290
+ 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}`)),
291
+ marquee && marquee.page === pageIndex && /* @__PURE__ */ jsx("div", { className: "marquee-rect", style: {
292
+ left: `${marquee.x1}%`,
293
+ top: `${marquee.y1}%`,
294
+ width: `${marquee.x2 - marquee.x1}%`,
295
+ height: `${marquee.y2 - marquee.y1}%`
296
+ } })
235
297
  ]
236
298
  },
237
299
  pageIndex
238
300
  );
239
- }) });
301
+ }) }) });
302
+ }
303
+ var SNAP_THRESHOLD = 1.2;
304
+ function snapAndGuide(field, others, page) {
305
+ const fx = field.x, fy = field.y, fw = field.width, fh = field.height;
306
+ const fcx = fx + fw / 2, fcy = fy + fh / 2;
307
+ let bestSnapX = null;
308
+ let bestSnapY = null;
309
+ for (const o of others) {
310
+ const ox = o.x, oy = o.y, ow = o.width, oh = o.height;
311
+ const ocx = ox + ow / 2, ocy = oy + oh / 2;
312
+ const xCandidates = [
313
+ [fx, ox, ox],
314
+ // left ↔ left
315
+ [fx + fw, ox + ow, ox + ow],
316
+ // right ↔ right
317
+ [fcx, ocx, ocx],
318
+ // center ↔ center
319
+ [fx, ox + ow, ox + ow],
320
+ // left ↔ right
321
+ [fx + fw, ox, ox]
322
+ // right ↔ left
323
+ ];
324
+ for (const [fe, te, gx] of xCandidates) {
325
+ const d = Math.abs(fe - te);
326
+ if (d < SNAP_THRESHOLD && (!bestSnapX || d < bestSnapX.dist)) {
327
+ bestSnapX = { offset: te - fe, guideX: gx, dist: d };
328
+ }
329
+ }
330
+ const yCandidates = [
331
+ [fy, oy, oy],
332
+ // top ↔ top
333
+ [fy + fh, oy + oh, oy + oh],
334
+ // bottom ↔ bottom
335
+ [fcy, ocy, ocy],
336
+ // center ↔ center
337
+ [fy, oy + oh, oy + oh],
338
+ // top ↔ bottom
339
+ [fy + fh, oy, oy]
340
+ // bottom ↔ top
341
+ ];
342
+ for (const [fe, te, gy] of yCandidates) {
343
+ const d = Math.abs(fe - te);
344
+ if (d < SNAP_THRESHOLD && (!bestSnapY || d < bestSnapY.dist)) {
345
+ bestSnapY = { offset: te - fe, guideY: gy, dist: d };
346
+ }
347
+ }
348
+ }
349
+ const snappedX = bestSnapX ? fx + bestSnapX.offset : fx;
350
+ const snappedY = bestSnapY ? fy + bestSnapY.offset : fy;
351
+ const guides = [];
352
+ if (bestSnapX) guides.push({ page, x: bestSnapX.guideX });
353
+ if (bestSnapY) guides.push({ page, y: bestSnapY.guideY });
354
+ return {
355
+ x: Math.max(0, Math.min(100 - fw, snappedX)),
356
+ y: Math.max(0, Math.min(100 - fh, snappedY)),
357
+ guides
358
+ };
240
359
  }
241
360
  function FieldOverlayItem({
242
361
  field,
@@ -246,6 +365,11 @@ function FieldOverlayItem({
246
365
  onMove,
247
366
  onResize,
248
367
  onGroupMove,
368
+ onMoveStart,
369
+ onMoveEnd,
370
+ otherFields,
371
+ setGuides,
372
+ pageIndex,
249
373
  selectedIds,
250
374
  mode,
251
375
  currentSigner,
@@ -253,19 +377,28 @@ function FieldOverlayItem({
253
377
  }) {
254
378
  const overlayRef = useRef(null);
255
379
  const dragStartRef = useRef(null);
380
+ const dragPosRef = useRef({ x: field.x, y: field.y });
381
+ const didDragRef = useRef(false);
256
382
  const resizeStartRef = useRef(null);
257
383
  const isRedact = field.type === "blackout" || field.type === "whiteout";
258
384
  const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
259
385
  const isInactiveSigner = mode === "signer" && !isRedact && field.assignee !== currentSigner;
260
- const color = isInactiveSigner ? "#cccccc" : getSignerColor(field.assignee);
386
+ const isFilled = mode === "signer" && isEditable && !isRedact && (field.type === "checkbox" ? true : field.formula ? true : !!field.value);
387
+ const color = isInactiveSigner ? "#cccccc" : isFilled ? "#22c55e" : getSignerColor(field.assignee);
261
388
  const isPreFilled = !isEditable && !!field.value;
262
389
  const handleMouseDown = useCallback((e) => {
263
390
  if (mode !== "designer" || !onMove) return;
264
391
  e.preventDefault();
265
392
  e.stopPropagation();
266
- onSelect(e);
393
+ const alreadyInSelection = selectedIds.has(field.id) && selectedIds.size > 1;
394
+ if (!alreadyInSelection) {
395
+ onSelect(e);
396
+ }
397
+ if (field.locked) return;
398
+ onMoveStart?.();
267
399
  const pageEl = overlayRef.current?.closest(".pdf-page");
268
400
  if (!pageEl) return;
401
+ didDragRef.current = false;
269
402
  dragStartRef.current = {
270
403
  startX: e.clientX,
271
404
  startY: e.clientY,
@@ -274,6 +407,7 @@ function FieldOverlayItem({
274
407
  };
275
408
  const handleMouseMove = (ev) => {
276
409
  if (!dragStartRef.current) return;
410
+ didDragRef.current = true;
277
411
  const rect = pageEl.getBoundingClientRect();
278
412
  const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
279
413
  const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
@@ -284,21 +418,38 @@ function FieldOverlayItem({
284
418
  } else {
285
419
  const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
286
420
  const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
421
+ dragPosRef.current = { x: newX, y: newY };
287
422
  onMove(field.id, field.page, newX, newY);
423
+ if (ev.shiftKey && otherFields && setGuides && pageIndex !== void 0) {
424
+ const { guides: g } = snapAndGuide({ x: newX, y: newY, width: field.width, height: field.height }, otherFields, pageIndex);
425
+ setGuides(g);
426
+ } else {
427
+ setGuides?.([]);
428
+ }
288
429
  }
289
430
  };
290
- const handleMouseUp = () => {
431
+ const handleMouseUp = (ev) => {
432
+ if (ev.shiftKey && didDragRef.current && otherFields && onMove && pageIndex !== void 0 && !isMultiSelected) {
433
+ const pos = dragPosRef.current;
434
+ const snap = snapAndGuide({ x: pos.x, y: pos.y, width: field.width, height: field.height }, otherFields, pageIndex);
435
+ if (snap.x !== pos.x || snap.y !== pos.y) {
436
+ onMove(field.id, field.page, snap.x, snap.y);
437
+ }
438
+ }
291
439
  dragStartRef.current = null;
292
440
  window.removeEventListener("mousemove", handleMouseMove);
293
441
  window.removeEventListener("mouseup", handleMouseUp);
442
+ setGuides?.([]);
443
+ onMoveEnd?.();
294
444
  };
295
445
  window.addEventListener("mousemove", handleMouseMove);
296
446
  window.addEventListener("mouseup", handleMouseUp);
297
- }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
447
+ }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveStart, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
298
448
  const handleResizeMouseDown = useCallback((e) => {
299
- if (mode !== "designer" || !onResize) return;
449
+ if (mode !== "designer" || !onResize || field.locked) return;
300
450
  e.preventDefault();
301
451
  e.stopPropagation();
452
+ onMoveStart?.();
302
453
  const pageEl = overlayRef.current?.closest(".pdf-page");
303
454
  if (!pageEl) return;
304
455
  resizeStartRef.current = {
@@ -320,15 +471,16 @@ function FieldOverlayItem({
320
471
  resizeStartRef.current = null;
321
472
  window.removeEventListener("mousemove", handleMouseMove);
322
473
  window.removeEventListener("mouseup", handleMouseUp);
474
+ onMoveEnd?.();
323
475
  };
324
476
  window.addEventListener("mousemove", handleMouseMove);
325
477
  window.addEventListener("mouseup", handleMouseUp);
326
- }, [field, mode, onResize]);
478
+ }, [field, mode, onResize, onMoveStart, onMoveEnd]);
327
479
  return /* @__PURE__ */ jsxs(
328
480
  "div",
329
481
  {
330
482
  ref: overlayRef,
331
- className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""}`,
483
+ className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""} ${field.locked ? "locked" : ""}`,
332
484
  style: {
333
485
  left: `${field.x}%`,
334
486
  top: `${field.y}%`,
@@ -337,18 +489,18 @@ function FieldOverlayItem({
337
489
  borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
338
490
  borderStyle: isRedact ? "dashed" : "solid",
339
491
  backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isInactiveSigner ? "#f5f5f5" : isSelected ? `${color}22` : `${color}11`,
340
- cursor: mode === "designer" ? "move" : isInactiveSigner ? "default" : "default",
492
+ cursor: mode === "designer" ? field.locked ? "not-allowed" : "move" : "default",
341
493
  pointerEvents: mode === "signer" && (isRedact || isInactiveSigner) ? "none" : void 0
342
494
  },
343
495
  onClick: (e) => {
344
496
  e.stopPropagation();
345
- onSelect(e);
497
+ if (!didDragRef.current) onSelect(e);
346
498
  },
347
499
  onMouseDown: handleMouseDown,
348
500
  children: [
349
501
  mode === "designer" && !isRedact && /* @__PURE__ */ jsx("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
350
502
  renderContent ? renderContent(field) : /* @__PURE__ */ jsx("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
351
- mode === "designer" && isSelected && /* @__PURE__ */ jsx(
503
+ mode === "designer" && isSelected && !field.locked && /* @__PURE__ */ jsx(
352
504
  "div",
353
505
  {
354
506
  className: "field-resize-handle",
@@ -361,127 +513,320 @@ function FieldOverlayItem({
361
513
  }
362
514
 
363
515
  // src/components/pdf-builder/FieldPropertyPanel.tsx
364
- import { useState } from "react";
365
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
516
+ import { useState as useState2 } from "react";
517
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
366
518
  var INK_COLORS = [
367
519
  { value: "#000000", label: "Black" },
368
520
  { value: "#1a56db", label: "Blue" }
369
521
  ];
370
- function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
522
+ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillContent, pageSize }) {
371
523
  const color = getSignerColor(field.assignee);
372
524
  const isRedactField = field.type === "blackout" || field.type === "whiteout";
373
525
  const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
374
526
  const showInkColor = field.type === "text" || field.type === "dropdown" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
375
- const [newOption, setNewOption] = useState("");
527
+ const [newOption, setNewOption] = useState2("");
376
528
  return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
377
529
  /* @__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
530
  /* @__PURE__ */ jsx2(
384
531
  "input",
385
532
  {
386
- type: "text",
533
+ className: "panel-header-label",
534
+ style: { color },
387
535
  value: field.label,
388
536
  onChange: (e) => onUpdate(field.id, { label: e.target.value })
389
537
  }
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
- )
538
+ ),
539
+ /* @__PURE__ */ jsxs2("div", { className: "panel-header-actions", children: [
540
+ /* @__PURE__ */ jsx2(
541
+ "button",
542
+ {
543
+ onClick: () => onUpdate(field.id, { locked: !field.locked }),
544
+ className: `panel-icon-btn ${field.locked ? "active" : ""}`,
545
+ title: field.locked ? "Unlock" : "Lock",
546
+ children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: field.locked ? /* @__PURE__ */ jsxs2(Fragment, { children: [
547
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
548
+ /* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0V6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
549
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
550
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
551
+ /* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
552
+ ] }) })
553
+ }
554
+ ),
555
+ /* @__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" }) }) })
556
+ ] })
426
557
  ] }),
427
- !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
428
- /* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
558
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
559
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Assigned To" }),
429
560
  /* @__PURE__ */ jsx2(
430
561
  "select",
431
562
  {
563
+ className: "panel-assignee-select",
432
564
  value: field.assignee,
433
565
  onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
566
+ style: { borderColor: color, color },
434
567
  children: signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
435
568
  }
436
569
  )
437
570
  ] }),
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
- )
571
+ /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
572
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Content" }),
573
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
574
+ /* @__PURE__ */ jsx2("label", { children: "Field Type" }),
575
+ /* @__PURE__ */ jsxs2("select", { value: field.type, onChange: (e) => onUpdate(field.id, { type: e.target.value }), children: [
576
+ /* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
577
+ /* @__PURE__ */ jsx2("option", { value: "dropdown", children: "Dropdown" }),
578
+ /* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
579
+ /* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
580
+ /* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
581
+ /* @__PURE__ */ jsx2("option", { value: "initials", children: "Initials" })
582
+ ] })
583
+ ] }),
584
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
585
+ /* @__PURE__ */ jsx2("label", { children: "Text Type" }),
586
+ /* @__PURE__ */ jsxs2("select", { value: field.textSubtype || "freeform", onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }), children: [
587
+ /* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
588
+ /* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
589
+ /* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
590
+ /* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
591
+ /* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
592
+ ] })
593
+ ] }),
594
+ !isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
595
+ /* @__PURE__ */ jsx2("label", { children: "Placeholder" }),
596
+ /* @__PURE__ */ jsx2("input", { type: "text", value: field.placeholder, onChange: (e) => onUpdate(field.id, { placeholder: e.target.value }) })
597
+ ] }),
598
+ !isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
599
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", checked: field.required, onChange: (e) => onUpdate(field.id, { required: e.target.checked }) }),
600
+ "Required"
601
+ ] }) }),
602
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
603
+ /* @__PURE__ */ jsx2("label", { children: "Formula" }),
604
+ /* @__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}}" }),
605
+ field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Auto-computed. Signer cannot edit." })
606
+ ] }),
607
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
608
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
609
+ /* @__PURE__ */ jsx2("label", { children: "Min Chars" }),
610
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.minLength || 0, onChange: (e) => onUpdate(field.id, { minLength: Number(e.target.value) }) })
611
+ ] }),
612
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
613
+ /* @__PURE__ */ jsx2("label", { children: "Max Chars" }),
614
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
615
+ ] })
616
+ ] }),
617
+ (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
618
+ /* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
619
+ /* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
620
+ (field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
621
+ /* @__PURE__ */ jsx2("span", { children: opt }),
622
+ /* @__PURE__ */ jsx2("button", { className: "panel-option-remove", onClick: () => {
623
+ const updated = (field.options || []).filter((_, j) => j !== i);
624
+ onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
625
+ }, children: "\xD7" })
626
+ ] }, i)),
627
+ /* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
628
+ /* @__PURE__ */ jsx2("input", { type: "text", value: newOption, onChange: (e) => setNewOption(e.target.value), onKeyDown: (e) => {
629
+ if (e.key === "Enter" && newOption.trim()) {
630
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
631
+ setNewOption("");
632
+ }
633
+ }, placeholder: "Add option..." }),
634
+ /* @__PURE__ */ jsx2("button", { onClick: () => {
635
+ if (newOption.trim()) {
636
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
637
+ setNewOption("");
638
+ }
639
+ }, children: "+" })
640
+ ] })
641
+ ] }),
642
+ field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
643
+ field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
644
+ ] }),
645
+ prefillContent
448
646
  ] }),
449
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
450
- /* @__PURE__ */ jsx2("label", { children: "Formula" }),
451
- /* @__PURE__ */ jsx2(
452
- "input",
647
+ (isTextField || showInkColor) && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
648
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Style" }),
649
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
650
+ /* @__PURE__ */ jsx2("label", { children: "Font" }),
651
+ /* @__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)) })
652
+ ] }),
653
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
654
+ /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
655
+ /* @__PURE__ */ jsx2("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
656
+ ] }),
657
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
658
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
659
+ /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
660
+ /* @__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) }) })
661
+ ] }),
662
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
663
+ /* @__PURE__ */ jsx2("label", { children: "Line Height" }),
664
+ /* @__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) }) })
665
+ ] })
666
+ ] }),
667
+ showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
668
+ /* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
669
+ /* @__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)) })
670
+ ] })
671
+ ] }),
672
+ /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
673
+ /* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Position & Size" }),
674
+ (() => {
675
+ const pw = pageSize?.width || 612;
676
+ const ph = pageSize?.height || 792;
677
+ const toPt = (pct, dim) => Math.round(pct / 100 * dim * 10) / 10;
678
+ const fromPt = (pt, dim) => pt / dim * 100;
679
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
680
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
681
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
682
+ /* @__PURE__ */ jsx2("label", { children: "X (pt)" }),
683
+ /* @__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) }) })
684
+ ] }),
685
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
686
+ /* @__PURE__ */ jsx2("label", { children: "Y (pt)" }),
687
+ /* @__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) }) })
688
+ ] })
689
+ ] }),
690
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
691
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
692
+ /* @__PURE__ */ jsx2("label", { children: "Width (pt)" }),
693
+ /* @__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) }) })
694
+ ] }),
695
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
696
+ /* @__PURE__ */ jsx2("label", { children: "Height (pt)" }),
697
+ /* @__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) }) })
698
+ ] })
699
+ ] }),
700
+ /* @__PURE__ */ jsxs2("div", { className: "panel-hint", children: [
701
+ pw,
702
+ " \xD7 ",
703
+ ph,
704
+ " pt \xB7 Page ",
705
+ field.page + 1,
706
+ " \xB7 ",
707
+ field.id.slice(0, 8)
708
+ ] })
709
+ ] });
710
+ })()
711
+ ] })
712
+ ] });
713
+ }
714
+ function sharedValue(items) {
715
+ if (items.length === 0) return void 0;
716
+ return items.every((v) => v === items[0]) ? items[0] : void 0;
717
+ }
718
+ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete }) {
719
+ const count = fields.length;
720
+ const hasText = fields.some((f) => f.type === "text" || f.type === "dropdown" || f.type === "signed-date");
721
+ const allNonRedact = fields.every((f) => f.type !== "blackout" && f.type !== "whiteout");
722
+ const sharedAssignee = sharedValue(fields.map((f) => f.assignee));
723
+ const sharedRequired = sharedValue(fields.map((f) => f.required));
724
+ const sharedFontSize = sharedValue(fields.map((f) => f.fontSize));
725
+ const sharedFontFamily = sharedValue(fields.map((f) => f.fontFamily || "Helvetica"));
726
+ const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
727
+ const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
728
+ const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
729
+ return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
730
+ /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
731
+ /* @__PURE__ */ jsxs2("h3", { children: [
732
+ count,
733
+ " fields selected"
734
+ ] }),
735
+ /* @__PURE__ */ jsx2("button", { onClick: onDelete, className: "panel-delete-btn", children: "Delete All" })
736
+ ] }),
737
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
738
+ /* @__PURE__ */ jsx2("label", { children: "Align" }),
739
+ /* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
740
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("left"), title: "Align left edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
741
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
742
+ /* @__PURE__ */ jsx2("rect", { x: "4", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
743
+ /* @__PURE__ */ jsx2("rect", { x: "4", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
744
+ ] }) }),
745
+ /* @__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: [
746
+ /* @__PURE__ */ jsx2("rect", { x: "6.25", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
747
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "10", height: "3", fill: "currentColor", opacity: "0.5" }),
748
+ /* @__PURE__ */ jsx2("rect", { x: "3.5", y: "7", width: "7", height: "3", fill: "currentColor", opacity: "0.5" })
749
+ ] }) }),
750
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("right"), title: "Align right edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
751
+ /* @__PURE__ */ jsx2("rect", { x: "11.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
752
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
753
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
754
+ ] }) }),
755
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("top"), title: "Align top edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
756
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "1", width: "14", height: "1.5", fill: "currentColor" }),
757
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
758
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "4", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
759
+ ] }) }),
760
+ /* @__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: [
761
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "6.25", width: "14", height: "1.5", fill: "currentColor" }),
762
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "1", width: "3", height: "12", fill: "currentColor", opacity: "0.5" }),
763
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "3", width: "3", height: "8", fill: "currentColor", opacity: "0.5" })
764
+ ] }) }),
765
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("bottom"), title: "Align bottom edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
766
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "11.5", width: "14", height: "1.5", fill: "currentColor" }),
767
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
768
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "5", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
769
+ ] }) })
770
+ ] }),
771
+ count > 2 && /* @__PURE__ */ jsxs2(Fragment, { children: [
772
+ /* @__PURE__ */ jsx2("label", { style: { marginTop: "0.4rem" }, children: "Distribute" }),
773
+ /* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
774
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-h"), title: "Distribute horizontally", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
775
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
776
+ /* @__PURE__ */ jsx2("rect", { x: "12.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
777
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "3", width: "4", height: "8", fill: "currentColor", opacity: "0.5" })
778
+ ] }) }),
779
+ /* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-v"), title: "Distribute vertically", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
780
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "14", height: "1.5", fill: "currentColor" }),
781
+ /* @__PURE__ */ jsx2("rect", { x: "0", y: "12.5", width: "14", height: "1.5", fill: "currentColor" }),
782
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "5", width: "8", height: "4", fill: "currentColor", opacity: "0.5" })
783
+ ] }) })
784
+ ] })
785
+ ] })
786
+ ] }),
787
+ allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
788
+ /* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
789
+ /* @__PURE__ */ jsxs2(
790
+ "select",
453
791
  {
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}}"
792
+ value: sharedAssignee || "",
793
+ onChange: (e) => onUpdate({ assignee: e.target.value }),
794
+ children: [
795
+ !sharedAssignee && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
796
+ signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
797
+ ]
458
798
  }
459
- ),
460
- field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
799
+ )
461
800
  ] }),
462
- !isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
801
+ allNonRedact && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
463
802
  /* @__PURE__ */ jsx2(
464
803
  "input",
465
804
  {
466
805
  type: "checkbox",
467
- checked: field.required,
468
- onChange: (e) => onUpdate(field.id, { required: e.target.checked })
806
+ checked: sharedRequired ?? false,
807
+ ref: (el) => {
808
+ if (el) el.indeterminate = sharedRequired === void 0;
809
+ },
810
+ onChange: (e) => onUpdate({ required: e.target.checked })
469
811
  }
470
812
  ),
471
813
  "Required"
472
814
  ] }) }),
473
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
815
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
474
816
  /* @__PURE__ */ jsx2("label", { children: "Font" }),
475
- /* @__PURE__ */ jsx2(
817
+ /* @__PURE__ */ jsxs2(
476
818
  "select",
477
819
  {
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))
820
+ value: sharedFontFamily || "",
821
+ onChange: (e) => onUpdate({ fontFamily: e.target.value }),
822
+ children: [
823
+ !sharedFontFamily && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
824
+ FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
825
+ ]
481
826
  }
482
827
  )
483
828
  ] }),
484
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
829
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
485
830
  /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
486
831
  /* @__PURE__ */ jsx2(
487
832
  "input",
@@ -489,12 +834,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
489
834
  type: "number",
490
835
  min: "6",
491
836
  max: "72",
492
- value: field.fontSize,
493
- onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) })
837
+ value: sharedFontSize ?? "",
838
+ placeholder: "Mixed",
839
+ onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
494
840
  }
495
841
  )
496
842
  ] }),
497
- isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
843
+ hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
498
844
  /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
499
845
  /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
500
846
  /* @__PURE__ */ jsx2(
@@ -504,8 +850,9 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
504
850
  min: "0",
505
851
  max: "20",
506
852
  step: "0.5",
507
- value: field.letterSpacing || 0,
508
- onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) })
853
+ value: sharedLetterSpacing ?? "",
854
+ placeholder: "Mixed",
855
+ onChange: (e) => onUpdate({ letterSpacing: Number(e.target.value) })
509
856
  }
510
857
  )
511
858
  ] }),
@@ -518,93 +865,31 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
518
865
  min: "0.8",
519
866
  max: "3",
520
867
  step: "0.1",
521
- value: field.lineHeight || 1.2,
522
- onChange: (e) => onUpdate(field.id, { lineHeight: Number(e.target.value) })
868
+ value: sharedLineHeight ?? "",
869
+ placeholder: "Mixed",
870
+ onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
523
871
  }
524
872
  )
525
873
  ] })
526
874
  ] }),
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: [
875
+ allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
541
876
  /* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
542
877
  /* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2(
543
878
  "button",
544
879
  {
545
- className: `ink-color-swatch ${(field.inkColor || "#000000") === c.value ? "active" : ""}`,
880
+ className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
546
881
  style: { backgroundColor: c.value },
547
- onClick: () => onUpdate(field.id, { inkColor: c.value }),
882
+ onClick: () => onUpdate({ inkColor: c.value }),
548
883
  title: c.label
549
884
  },
550
885
  c.value
551
886
  )) })
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
887
  ] })
603
888
  ] });
604
889
  }
605
890
 
606
891
  // src/components/pdf-builder/SignatureCanvas.tsx
607
- import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect } from "react";
892
+ import { useRef as useRef2, useState as useState3, useCallback as useCallback2, useEffect } from "react";
608
893
  import { getStroke } from "perfect-freehand";
609
894
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
610
895
  function getSvgPathFromStroke(stroke) {
@@ -629,9 +914,9 @@ function SignatureCanvas({
629
914
  inkColor = "#000000"
630
915
  }) {
631
916
  const canvasRef = useRef2(null);
632
- const [paths, setPaths] = useState2([]);
633
- const [currentPath, setCurrentPath] = useState2(null);
634
- const [isEmpty, setIsEmpty] = useState2(!initialValue);
917
+ const [paths, setPaths] = useState3([]);
918
+ const [currentPath, setCurrentPath] = useState3(null);
919
+ const [isEmpty, setIsEmpty] = useState3(!initialValue);
635
920
  useEffect(() => {
636
921
  if (initialValue && canvasRef.current) {
637
922
  const ctx = canvasRef.current.getContext("2d");
@@ -743,8 +1028,94 @@ function isValidApiKey(key) {
743
1028
  return VALID_KEYS.has(key);
744
1029
  }
745
1030
 
1031
+ // src/hooks/useHistory.ts
1032
+ import { useState as useState4, useCallback as useCallback3, useRef as useRef3 } from "react";
1033
+ function useHistory(initialState, maxHistory = 50) {
1034
+ const [state, setState] = useState4({
1035
+ past: [],
1036
+ present: initialState,
1037
+ future: []
1038
+ });
1039
+ const batchRef = useRef3(false);
1040
+ const batchStartRef = useRef3(null);
1041
+ const set = useCallback3((updater) => {
1042
+ setState((prev) => {
1043
+ const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
1044
+ if (newPresent === prev.present) return prev;
1045
+ if (batchRef.current) {
1046
+ return { ...prev, present: newPresent };
1047
+ }
1048
+ return {
1049
+ past: [...prev.past.slice(-maxHistory), prev.present],
1050
+ present: newPresent,
1051
+ future: []
1052
+ };
1053
+ });
1054
+ }, [maxHistory]);
1055
+ const setWithoutHistory = useCallback3((updater) => {
1056
+ setState((prev) => {
1057
+ const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
1058
+ if (newPresent === prev.present) return prev;
1059
+ return { ...prev, present: newPresent };
1060
+ });
1061
+ }, []);
1062
+ const beginBatch = useCallback3(() => {
1063
+ batchRef.current = true;
1064
+ setState((prev) => {
1065
+ batchStartRef.current = prev.present;
1066
+ return prev;
1067
+ });
1068
+ }, []);
1069
+ const commitBatch = useCallback3(() => {
1070
+ batchRef.current = false;
1071
+ setState((prev) => {
1072
+ const startState = batchStartRef.current;
1073
+ batchStartRef.current = null;
1074
+ if (!startState || startState === prev.present) return prev;
1075
+ return {
1076
+ past: [...prev.past.slice(-maxHistory), startState],
1077
+ present: prev.present,
1078
+ future: []
1079
+ };
1080
+ });
1081
+ }, [maxHistory]);
1082
+ const undo = useCallback3(() => {
1083
+ setState((prev) => {
1084
+ if (prev.past.length === 0) return prev;
1085
+ const previous = prev.past[prev.past.length - 1];
1086
+ return {
1087
+ past: prev.past.slice(0, -1),
1088
+ present: previous,
1089
+ future: [prev.present, ...prev.future]
1090
+ };
1091
+ });
1092
+ }, []);
1093
+ const redo = useCallback3(() => {
1094
+ setState((prev) => {
1095
+ if (prev.future.length === 0) return prev;
1096
+ const next = prev.future[0];
1097
+ return {
1098
+ past: [...prev.past, prev.present],
1099
+ present: next,
1100
+ future: prev.future.slice(1)
1101
+ };
1102
+ });
1103
+ }, []);
1104
+ return {
1105
+ state: state.present,
1106
+ set,
1107
+ setWithoutHistory,
1108
+ beginBatch,
1109
+ commitBatch,
1110
+ undo,
1111
+ redo,
1112
+ canUndo: state.past.length > 0,
1113
+ canRedo: state.future.length > 0
1114
+ };
1115
+ }
1116
+
746
1117
  // src/components/pdf-builder/DesignerView.tsx
747
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1118
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
748
1119
  var FIELD_TYPE_META = [
749
1120
  { type: "text", label: "Text", icon: "T" },
750
1121
  { type: "dropdown", label: "Dropdown", icon: "\u25BE" },
@@ -773,23 +1144,27 @@ function DesignerView({
773
1144
  ] })
774
1145
  ] }) });
775
1146
  }
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({});
1147
+ const [pages, setPages] = useState5([]);
1148
+ const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, beginBatch, commitBatch, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
1149
+ const [selectedFieldIds, setSelectedFieldIds] = useState5(/* @__PURE__ */ new Set());
1150
+ const [signerRoles, setSignerRoles] = useState5(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
1151
+ const [activeRole, setActiveRole] = useState5("Sender");
1152
+ const [activeFieldType, setActiveFieldType] = useState5("text");
1153
+ const [loading, setLoading] = useState5(false);
1154
+ const [pdfSource, setPdfSource] = useState5(null);
1155
+ const [rightTab, setRightTab] = useState5("properties");
1156
+ const [isAddingRole, setIsAddingRole] = useState5(false);
1157
+ const [newRoleName, setNewRoleName] = useState5("");
1158
+ const [draggingFieldType, setDraggingFieldType] = useState5(null);
1159
+ const [panelWidth, setPanelWidth] = useState5(380);
1160
+ const [zoom, setZoom] = useState5(1);
1161
+ const [isPanning, setIsPanning] = useState5(false);
1162
+ const panRef = useRef4(null);
1163
+ const pdfAreaRef = useRef4(null);
1164
+ const [clipboardFields, setClipboardFields] = useState5([]);
1165
+ const dragGhostRef = useRef4(null);
1166
+ const resizingRef = useRef4(false);
1167
+ const lastStylesRef = useRef4({});
793
1168
  useEffect2(() => {
794
1169
  const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
795
1170
  if (pdfUrl) {
@@ -814,7 +1189,7 @@ function DesignerView({
814
1189
  window.addEventListener("message", handleMessage);
815
1190
  return () => window.removeEventListener("message", handleMessage);
816
1191
  }, [initialPdfUrl, initialTemplate]);
817
- const loadPdf = useCallback3(async (source) => {
1192
+ const loadPdf = useCallback4(async (source) => {
818
1193
  setLoading(true);
819
1194
  try {
820
1195
  const rendered = await renderPdfPages(source);
@@ -826,7 +1201,7 @@ function DesignerView({
826
1201
  setLoading(false);
827
1202
  }
828
1203
  }, []);
829
- const handleFileUpload = useCallback3((e) => {
1204
+ const handleFileUpload = useCallback4((e) => {
830
1205
  const file = e.target.files?.[0];
831
1206
  if (!file) return;
832
1207
  const reader = new FileReader();
@@ -835,7 +1210,7 @@ function DesignerView({
835
1210
  };
836
1211
  reader.readAsArrayBuffer(file);
837
1212
  }, [loadPdf]);
838
- const handlePageClick = useCallback3((page, x, y) => {
1213
+ const handlePageClick = useCallback4((page, x, y) => {
839
1214
  const field = createField(activeFieldType, activeRole, page, x, y, fields);
840
1215
  const sticky = lastStylesRef.current[activeFieldType];
841
1216
  const styledField = sticky ? { ...field, ...sticky } : field;
@@ -843,11 +1218,14 @@ function DesignerView({
843
1218
  setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
844
1219
  setRightTab("properties");
845
1220
  }, [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) => {
1221
+ const handleFieldMove = useCallback4((id, page, x, y) => {
1222
+ setFieldsSilent((prev) => prev.map((f) => {
1223
+ if (f.id !== id || f.locked) return f;
1224
+ return { ...f, page, x, y };
1225
+ }));
1226
+ }, [setFieldsSilent]);
1227
+ const handleFieldResize = useCallback4((id, width, height) => {
1228
+ setFieldsSilent((prev) => {
851
1229
  const field = prev.find((f) => f.id === id);
852
1230
  if (field) {
853
1231
  const existing = lastStylesRef.current[field.type] || {};
@@ -855,8 +1233,8 @@ function DesignerView({
855
1233
  }
856
1234
  return prev.map((f) => f.id === id ? { ...f, width, height } : f);
857
1235
  });
858
- }, []);
859
- const handleFieldUpdate = useCallback3((id, updates) => {
1236
+ }, [setFieldsSilent]);
1237
+ const handleFieldUpdate = useCallback4((id, updates) => {
860
1238
  setFields((prev) => {
861
1239
  if (updates.label !== void 0) {
862
1240
  const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
@@ -876,7 +1254,75 @@ function DesignerView({
876
1254
  return updated;
877
1255
  });
878
1256
  }, []);
879
- const handleFieldDelete = useCallback3((id) => {
1257
+ const handleBulkUpdate = useCallback4((updates) => {
1258
+ setFields((prev) => prev.map((f) => selectedFieldIds.has(f.id) ? { ...f, ...updates } : f));
1259
+ }, [selectedFieldIds]);
1260
+ const handleAlign = useCallback4((type) => {
1261
+ setFields((prev) => {
1262
+ const selected = prev.filter((f) => selectedFieldIds.has(f.id));
1263
+ if (selected.length < 2) return prev;
1264
+ let updates = {};
1265
+ if (type === "left") {
1266
+ const minX = Math.min(...selected.map((f) => f.x));
1267
+ selected.forEach((f) => {
1268
+ updates[f.id] = { x: minX };
1269
+ });
1270
+ } else if (type === "right") {
1271
+ const maxRight = Math.max(...selected.map((f) => f.x + f.width));
1272
+ selected.forEach((f) => {
1273
+ updates[f.id] = { x: maxRight - f.width };
1274
+ });
1275
+ } else if (type === "top") {
1276
+ const minY = Math.min(...selected.map((f) => f.y));
1277
+ selected.forEach((f) => {
1278
+ updates[f.id] = { y: minY };
1279
+ });
1280
+ } else if (type === "bottom") {
1281
+ const maxBottom = Math.max(...selected.map((f) => f.y + f.height));
1282
+ selected.forEach((f) => {
1283
+ updates[f.id] = { y: maxBottom - f.height };
1284
+ });
1285
+ } else if (type === "center-h") {
1286
+ const centers = selected.map((f) => f.x + f.width / 2);
1287
+ const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
1288
+ selected.forEach((f) => {
1289
+ updates[f.id] = { x: avgCenter - f.width / 2 };
1290
+ });
1291
+ } else if (type === "center-v") {
1292
+ const centers = selected.map((f) => f.y + f.height / 2);
1293
+ const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
1294
+ selected.forEach((f) => {
1295
+ updates[f.id] = { y: avgCenter - f.height / 2 };
1296
+ });
1297
+ } else if (type === "distribute-h") {
1298
+ const sorted = [...selected].sort((a, b) => a.x - b.x);
1299
+ const first = sorted[0], last = sorted[sorted.length - 1];
1300
+ const totalSpan = last.x + last.width - first.x;
1301
+ const totalFieldWidth = sorted.reduce((s, f) => s + f.width, 0);
1302
+ const gap = (totalSpan - totalFieldWidth) / (sorted.length - 1);
1303
+ let cx = first.x;
1304
+ sorted.forEach((f) => {
1305
+ updates[f.id] = { x: cx };
1306
+ cx += f.width + gap;
1307
+ });
1308
+ } else if (type === "distribute-v") {
1309
+ const sorted = [...selected].sort((a, b) => a.y - b.y);
1310
+ const first = sorted[0], last = sorted[sorted.length - 1];
1311
+ const totalSpan = last.y + last.height - first.y;
1312
+ const totalFieldHeight = sorted.reduce((s, f) => s + f.height, 0);
1313
+ const gap = (totalSpan - totalFieldHeight) / (sorted.length - 1);
1314
+ let cy = first.y;
1315
+ sorted.forEach((f) => {
1316
+ updates[f.id] = { y: cy };
1317
+ cy += f.height + gap;
1318
+ });
1319
+ }
1320
+ return prev.map((f) => updates[f.id] ? { ...f, ...updates[f.id] } : f);
1321
+ });
1322
+ }, [selectedFieldIds]);
1323
+ const handleFieldDelete = useCallback4((id) => {
1324
+ const field = fields.find((f) => f.id === id);
1325
+ if (field?.locked) return;
880
1326
  setFields((prev) => prev.filter((f) => f.id !== id));
881
1327
  setSelectedFieldIds((prev) => {
882
1328
  const next = new Set(prev);
@@ -884,7 +1330,7 @@ function DesignerView({
884
1330
  return next;
885
1331
  });
886
1332
  }, []);
887
- const handleSelectField = useCallback3((id, e) => {
1333
+ const handleSelectField = useCallback4((id, e) => {
888
1334
  if (!id) {
889
1335
  setSelectedFieldIds(/* @__PURE__ */ new Set());
890
1336
  return;
@@ -918,15 +1364,15 @@ function DesignerView({
918
1364
  }
919
1365
  setRightTab("properties");
920
1366
  }, [selectedFieldIds]);
921
- const handleGroupMove = useCallback3((ids, dx, dy) => {
922
- setFields((prev) => prev.map((f) => {
923
- if (!ids.includes(f.id)) return f;
1367
+ const handleGroupMove = useCallback4((ids, dx, dy) => {
1368
+ setFieldsSilent((prev) => prev.map((f) => {
1369
+ if (!ids.includes(f.id) || f.locked) return f;
924
1370
  const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
925
1371
  const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
926
1372
  return { ...f, x: newX, y: newY };
927
1373
  }));
928
- }, []);
929
- const handleAddRole = useCallback3(() => {
1374
+ }, [setFieldsSilent]);
1375
+ const handleAddRole = useCallback4(() => {
930
1376
  const name = newRoleName.trim();
931
1377
  if (name && !signerRoles.includes(name)) {
932
1378
  setSignerRoles((prev) => [...prev, name]);
@@ -934,12 +1380,12 @@ function DesignerView({
934
1380
  setNewRoleName("");
935
1381
  setIsAddingRole(false);
936
1382
  }, [newRoleName, signerRoles]);
937
- const handleRemoveRole = useCallback3((role) => {
1383
+ const handleRemoveRole = useCallback4((role) => {
938
1384
  setSignerRoles((prev) => prev.filter((r) => r !== role));
939
1385
  setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
940
1386
  if (activeRole === role) setActiveRole(signerRoles[0]);
941
1387
  }, [signerRoles, activeRole]);
942
- const handleExport = useCallback3(() => {
1388
+ const handleExport = useCallback4(() => {
943
1389
  const template = {
944
1390
  fields,
945
1391
  signerRoles,
@@ -959,7 +1405,7 @@ function DesignerView({
959
1405
  }
960
1406
  window.parent?.postMessage({ type: "template-saved", template }, "*");
961
1407
  }, [fields, signerRoles, pdfSource, onSave]);
962
- const handlePaletteDragStart = useCallback3((e, type) => {
1408
+ const handlePaletteDragStart = useCallback4((e, type) => {
963
1409
  setDraggingFieldType(type);
964
1410
  e.dataTransfer.setData("application/exeq-field-type", type);
965
1411
  e.dataTransfer.effectAllowed = "copy";
@@ -976,14 +1422,14 @@ function DesignerView({
976
1422
  e.dataTransfer.setDragImage(ghost, 40, 16);
977
1423
  dragGhostRef.current = ghost;
978
1424
  }, [activeRole]);
979
- const handlePaletteDragEnd = useCallback3(() => {
1425
+ const handlePaletteDragEnd = useCallback4(() => {
980
1426
  setDraggingFieldType(null);
981
1427
  if (dragGhostRef.current) {
982
1428
  document.body.removeChild(dragGhostRef.current);
983
1429
  dragGhostRef.current = null;
984
1430
  }
985
1431
  }, []);
986
- const handleDropOnPage = useCallback3((page, x, y, fieldType) => {
1432
+ const handleDropOnPage = useCallback4((page, x, y, fieldType) => {
987
1433
  const field = createField(fieldType, activeRole, page, x, y, fields);
988
1434
  const sticky = lastStylesRef.current[fieldType];
989
1435
  const styledField = sticky ? { ...field, ...sticky } : field;
@@ -999,21 +1445,39 @@ function DesignerView({
999
1445
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1000
1446
  if (document.activeElement?.isContentEditable) return;
1001
1447
  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
- }
1448
+ setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
1449
+ setSelectedFieldIds((prev) => {
1450
+ const remaining = /* @__PURE__ */ new Set();
1451
+ fields.forEach((f) => {
1452
+ if (selectedFieldIds.has(f.id) && f.locked) remaining.add(f.id);
1453
+ });
1454
+ return remaining;
1455
+ });
1007
1456
  };
1008
1457
  window.addEventListener("keydown", handleKeyDown);
1009
1458
  return () => window.removeEventListener("keydown", handleKeyDown);
1010
- }, [selectedFieldIds]);
1459
+ }, [selectedFieldIds, fields]);
1011
1460
  useEffect2(() => {
1012
1461
  const handleKeyDown = (e) => {
1013
1462
  const tag = (document.activeElement?.tagName || "").toLowerCase();
1014
1463
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1015
1464
  if (document.activeElement?.isContentEditable) return;
1016
1465
  const isMod = e.metaKey || e.ctrlKey;
1466
+ if (isMod && e.key === "a") {
1467
+ e.preventDefault();
1468
+ setSelectedFieldIds(new Set(fields.map((f) => f.id)));
1469
+ return;
1470
+ }
1471
+ if (isMod && e.key === "z" && !e.shiftKey) {
1472
+ e.preventDefault();
1473
+ undoFields();
1474
+ return;
1475
+ }
1476
+ if (isMod && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1477
+ e.preventDefault();
1478
+ redoFields();
1479
+ return;
1480
+ }
1017
1481
  if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
1018
1482
  const selected = fields.filter((f) => selectedFieldIds.has(f.id));
1019
1483
  if (selected.length > 0) {
@@ -1040,7 +1504,75 @@ function DesignerView({
1040
1504
  window.addEventListener("keydown", handleKeyDown);
1041
1505
  return () => window.removeEventListener("keydown", handleKeyDown);
1042
1506
  }, [selectedFieldIds, fields, clipboardFields]);
1043
- const handleResizeStart = useCallback3((e) => {
1507
+ useEffect2(() => {
1508
+ const handleKeyDown = (e) => {
1509
+ const isMod = e.metaKey || e.ctrlKey;
1510
+ if (isMod && (e.key === "=" || e.key === "+")) {
1511
+ e.preventDefault();
1512
+ setZoom((z) => Math.min(3, z + 0.25));
1513
+ }
1514
+ if (isMod && e.key === "-") {
1515
+ e.preventDefault();
1516
+ setZoom((z) => Math.max(0.75, z - 0.25));
1517
+ }
1518
+ if (isMod && e.key === "0") {
1519
+ e.preventDefault();
1520
+ setZoom(1);
1521
+ }
1522
+ if (e.key === " ") {
1523
+ const tag = (document.activeElement?.tagName || "").toLowerCase();
1524
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
1525
+ e.preventDefault();
1526
+ e.stopPropagation();
1527
+ setIsPanning(true);
1528
+ }
1529
+ };
1530
+ const handleKeyUp = (e) => {
1531
+ if (e.key === " ") {
1532
+ e.preventDefault();
1533
+ setIsPanning(false);
1534
+ panRef.current = null;
1535
+ }
1536
+ };
1537
+ window.addEventListener("keydown", handleKeyDown, { capture: true });
1538
+ window.addEventListener("keyup", handleKeyUp, { capture: true });
1539
+ return () => {
1540
+ window.removeEventListener("keydown", handleKeyDown, { capture: true });
1541
+ window.removeEventListener("keyup", handleKeyUp, { capture: true });
1542
+ };
1543
+ }, []);
1544
+ useEffect2(() => {
1545
+ const el = pdfAreaRef.current;
1546
+ if (!el) return;
1547
+ const handleWheel = (e) => {
1548
+ if (e.ctrlKey || e.metaKey) {
1549
+ e.preventDefault();
1550
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
1551
+ setZoom((z) => Math.max(0.75, Math.min(3, z + delta)));
1552
+ }
1553
+ };
1554
+ el.addEventListener("wheel", handleWheel, { passive: false });
1555
+ return () => el.removeEventListener("wheel", handleWheel);
1556
+ }, [pages.length]);
1557
+ const handlePanMouseDown = useCallback4((e) => {
1558
+ if (!isPanning || !pdfAreaRef.current) return;
1559
+ e.preventDefault();
1560
+ panRef.current = {
1561
+ startX: e.clientX,
1562
+ startY: e.clientY,
1563
+ scrollLeft: pdfAreaRef.current.scrollLeft,
1564
+ scrollTop: pdfAreaRef.current.scrollTop
1565
+ };
1566
+ }, [isPanning]);
1567
+ const handlePanMouseMove = useCallback4((e) => {
1568
+ if (!panRef.current || !pdfAreaRef.current) return;
1569
+ pdfAreaRef.current.scrollLeft = panRef.current.scrollLeft - (e.clientX - panRef.current.startX);
1570
+ pdfAreaRef.current.scrollTop = panRef.current.scrollTop - (e.clientY - panRef.current.startY);
1571
+ }, []);
1572
+ const handlePanMouseUp = useCallback4(() => {
1573
+ panRef.current = null;
1574
+ }, []);
1575
+ const handleResizeStart = useCallback4((e) => {
1044
1576
  e.preventDefault();
1045
1577
  resizingRef.current = true;
1046
1578
  const startX = e.clientX;
@@ -1065,7 +1597,7 @@ function DesignerView({
1065
1597
  return a.x - b.x;
1066
1598
  });
1067
1599
  const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
1068
- const renderFieldContent = useCallback3((field) => {
1600
+ const renderFieldContent = useCallback4((field) => {
1069
1601
  if (field.type === "blackout" || field.type === "whiteout") {
1070
1602
  return null;
1071
1603
  }
@@ -1091,7 +1623,7 @@ function DesignerView({
1091
1623
  }
1092
1624
  );
1093
1625
  }, [fields]);
1094
- const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1626
+ const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1095
1627
  /* @__PURE__ */ jsxs4("label", { className: "header-btn header-btn-outline", children: [
1096
1628
  "Change PDF",
1097
1629
  /* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
@@ -1169,39 +1701,92 @@ function DesignerView({
1169
1701
  type
1170
1702
  ))
1171
1703
  ] }),
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: [
1704
+ /* @__PURE__ */ jsxs4(
1705
+ "div",
1706
+ {
1707
+ className: `designer-pdf-area ${isPanning ? "panning" : ""}`,
1708
+ ref: pdfAreaRef,
1709
+ onMouseDown: isPanning ? handlePanMouseDown : void 0,
1710
+ onMouseMove: isPanning ? handlePanMouseMove : void 0,
1711
+ onMouseUp: isPanning ? handlePanMouseUp : void 0,
1712
+ onMouseLeave: isPanning ? handlePanMouseUp : void 0,
1713
+ children: [
1714
+ loading && /* @__PURE__ */ jsx4("div", { className: "loading-indicator", children: "Loading PDF..." }),
1715
+ !pages.length && !loading && /* @__PURE__ */ jsxs4("div", { className: "empty-state", children: [
1716
+ /* @__PURE__ */ jsx4("h2", { children: "Open a PDF to get started" }),
1717
+ /* @__PURE__ */ jsx4("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
1718
+ /* @__PURE__ */ jsxs4("label", { className: "upload-btn upload-btn-large", children: [
1719
+ "Select PDF",
1720
+ /* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
1721
+ ] })
1722
+ ] }),
1723
+ pages.length > 0 && /* @__PURE__ */ jsx4(
1724
+ PdfViewer,
1725
+ {
1726
+ pages,
1727
+ fields,
1728
+ selectedFieldIds,
1729
+ onSelectField: handleSelectField,
1730
+ onFieldMove: handleFieldMove,
1731
+ onFieldResize: handleFieldResize,
1732
+ onGroupMove: handleGroupMove,
1733
+ onMoveStart: beginBatch,
1734
+ onMoveEnd: commitBatch,
1735
+ onPageClick: handlePageClick,
1736
+ onDropField: handleDropOnPage,
1737
+ onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
1738
+ mode: "designer",
1739
+ renderFieldContent,
1740
+ zoom
1741
+ }
1742
+ )
1743
+ ]
1744
+ }
1745
+ ),
1746
+ pages.length > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1200
1747
  /* @__PURE__ */ jsx4("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
1201
1748
  /* @__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 })
1749
+ /* @__PURE__ */ jsxs4("div", { className: "designer-panel-header", children: [
1750
+ /* @__PURE__ */ jsxs4("div", { className: "signer-role-indicator", children: [
1751
+ /* @__PURE__ */ jsx4("span", { className: "signer-role-indicator-label", children: "Editing as" }),
1752
+ /* @__PURE__ */ jsx4("strong", { children: activeRole })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsxs4("div", { className: "zoom-slider", children: [
1755
+ /* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.max(0.75, z - 0.25)), disabled: zoom <= 0.75, children: "\u2212" }),
1756
+ /* @__PURE__ */ jsx4(
1757
+ "input",
1758
+ {
1759
+ type: "range",
1760
+ min: "75",
1761
+ max: "300",
1762
+ step: "25",
1763
+ value: Math.round(zoom * 100),
1764
+ onChange: (e) => setZoom(Number(e.target.value) / 100)
1765
+ }
1766
+ ),
1767
+ /* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.min(3, z + 0.25)), disabled: zoom >= 3, children: "+" }),
1768
+ /* @__PURE__ */ jsx4("div", { className: "zoom-dropdown-wrapper", children: /* @__PURE__ */ jsxs4(
1769
+ "select",
1770
+ {
1771
+ className: "zoom-dropdown",
1772
+ value: Math.round(zoom * 100),
1773
+ onChange: (e) => setZoom(Number(e.target.value) / 100),
1774
+ children: [
1775
+ /* @__PURE__ */ jsx4("option", { value: "75", children: "75%" }),
1776
+ /* @__PURE__ */ jsx4("option", { value: "100", children: "100%" }),
1777
+ /* @__PURE__ */ jsx4("option", { value: "125", children: "125%" }),
1778
+ /* @__PURE__ */ jsx4("option", { value: "150", children: "150%" }),
1779
+ /* @__PURE__ */ jsx4("option", { value: "200", children: "200%" }),
1780
+ /* @__PURE__ */ jsx4("option", { value: "250", children: "250%" }),
1781
+ /* @__PURE__ */ jsx4("option", { value: "300", children: "300%" }),
1782
+ ![75, 100, 125, 150, 200, 250, 300].includes(Math.round(zoom * 100)) && /* @__PURE__ */ jsxs4("option", { value: Math.round(zoom * 100), children: [
1783
+ Math.round(zoom * 100),
1784
+ "%"
1785
+ ] })
1786
+ ]
1787
+ }
1788
+ ) })
1789
+ ] })
1205
1790
  ] }),
1206
1791
  /* @__PURE__ */ jsxs4("div", { className: "panel-tabs", children: [
1207
1792
  /* @__PURE__ */ jsx4(
@@ -1225,82 +1810,163 @@ function DesignerView({
1225
1810
  )
1226
1811
  ] }),
1227
1812
  /* @__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
1813
+ rightTab === "properties" && /* @__PURE__ */ jsx4(Fragment2, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ jsx4(
1814
+ BulkPropertyPanel,
1815
+ {
1816
+ fields: fields.filter((f) => selectedFieldIds.has(f.id)),
1817
+ signerRoles,
1818
+ onUpdate: handleBulkUpdate,
1819
+ onAlign: handleAlign,
1820
+ onDelete: () => {
1821
+ setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
1822
+ setSelectedFieldIds((prev) => {
1823
+ const remaining = /* @__PURE__ */ new Set();
1824
+ fields.forEach((f) => {
1825
+ if (prev.has(f.id) && f.locked) remaining.add(f.id);
1826
+ });
1827
+ return remaining;
1828
+ });
1242
1829
  }
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",
1830
+ }
1831
+ ) : selectedField ? /* @__PURE__ */ jsx4(Fragment2, { children: /* @__PURE__ */ jsx4(
1832
+ FieldPropertyPanel,
1833
+ {
1834
+ field: selectedField,
1835
+ signerRoles,
1836
+ onUpdate: handleFieldUpdate,
1837
+ onDelete: handleFieldDelete,
1838
+ pageSize: pages[selectedField.page] ? { width: pages[selectedField.page].pdfWidth, height: pages[selectedField.page].pdfHeight } : void 0,
1839
+ prefillContent: selectedField.type !== "blackout" && selectedField.type !== "whiteout" ? /* @__PURE__ */ jsxs4("div", { className: "prefill-section", children: [
1840
+ /* @__PURE__ */ jsx4("label", { children: "Pre-fill Value" }),
1841
+ selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ jsx4(
1842
+ SignatureCanvas,
1258
1843
  {
1259
- type: "checkbox",
1260
- checked: selectedField.value === "true",
1261
- onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
1844
+ width: panelWidth - 40,
1845
+ height: selectedField.type === "initials" ? 100 : 150,
1846
+ onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
1847
+ initialValue: selectedField.value,
1848
+ inkColor: selectedField.inkColor
1262
1849
  }
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",
1850
+ ) : selectedField.type === "checkbox" ? /* @__PURE__ */ jsxs4("label", { className: "panel-checkbox-label", children: [
1851
+ /* @__PURE__ */ jsx4(
1852
+ "input",
1853
+ {
1854
+ type: "checkbox",
1855
+ checked: selectedField.value === "true",
1856
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
1857
+ }
1858
+ ),
1859
+ "Checked"
1860
+ ] }) : /* @__PURE__ */ jsx4(
1861
+ "input",
1289
1862
  {
1290
- className: "field-list-dot",
1291
- style: { backgroundColor: getSignerColor(f.assignee) }
1863
+ type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
1864
+ value: selectedField.value,
1865
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
1866
+ placeholder: `Pre-fill ${selectedField.label}`,
1867
+ className: "prefill-input",
1868
+ style: { color: selectedField.inkColor || "#000000" }
1292
1869
  }
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
- )) }) })
1870
+ )
1871
+ ] }) : void 0
1872
+ }
1873
+ ) }) : /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
1874
+ 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) => {
1875
+ const isSel = selectedFieldIds.has(f.id);
1876
+ const isSingleSel = isSel && selectedFieldIds.size === 1;
1877
+ const isTextLike = f.type === "text" || f.type === "dropdown" || f.type === "signed-date";
1878
+ return /* @__PURE__ */ jsxs4(
1879
+ "div",
1880
+ {
1881
+ className: `field-list-item ${isSel ? "active" : ""}`,
1882
+ onClick: (e) => {
1883
+ if (e.target.tagName === "INPUT") return;
1884
+ if (e.metaKey || e.ctrlKey) {
1885
+ setSelectedFieldIds((prev) => {
1886
+ const next = new Set(prev);
1887
+ if (next.has(f.id)) next.delete(f.id);
1888
+ else next.add(f.id);
1889
+ return next;
1890
+ });
1891
+ } else if (e.shiftKey && selectedFieldIds.size > 0) {
1892
+ const ids = sortedFields.map((sf) => sf.id);
1893
+ const lastId = Array.from(selectedFieldIds).pop();
1894
+ const a = ids.indexOf(lastId);
1895
+ const b = ids.indexOf(f.id);
1896
+ if (a >= 0 && b >= 0) {
1897
+ const start = Math.min(a, b);
1898
+ const end = Math.max(a, b);
1899
+ setSelectedFieldIds((prev) => {
1900
+ const next = new Set(prev);
1901
+ ids.slice(start, end + 1).forEach((id) => next.add(id));
1902
+ return next;
1903
+ });
1904
+ }
1905
+ } else {
1906
+ setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
1907
+ }
1908
+ },
1909
+ children: [
1910
+ /* @__PURE__ */ jsx4(
1911
+ "span",
1912
+ {
1913
+ className: "field-list-dot",
1914
+ style: { backgroundColor: getSignerColor(f.assignee) }
1915
+ }
1916
+ ),
1917
+ /* @__PURE__ */ jsx4("span", { className: "field-list-name", children: f.label }),
1918
+ f.locked && /* @__PURE__ */ jsx4("span", { className: "field-list-lock", title: "Locked", children: "\u{1F512}" }),
1919
+ /* @__PURE__ */ jsxs4("span", { className: "field-list-page", children: [
1920
+ "p",
1921
+ f.page + 1
1922
+ ] }),
1923
+ f.required && /* @__PURE__ */ jsx4("span", { className: "field-list-required", children: "*" }),
1924
+ isSingleSel && isTextLike && /* @__PURE__ */ jsx4(
1925
+ "input",
1926
+ {
1927
+ className: "field-list-input",
1928
+ type: "text",
1929
+ value: f.value,
1930
+ placeholder: f.placeholder,
1931
+ maxLength: f.maxLength || void 0,
1932
+ autoFocus: true,
1933
+ onChange: (e) => handleFieldUpdate(f.id, { value: e.target.value }),
1934
+ onKeyDown: (e) => {
1935
+ if (e.key === "Tab") {
1936
+ e.preventDefault();
1937
+ const dir = e.shiftKey ? -1 : 1;
1938
+ const nextIdx = idx + dir;
1939
+ if (nextIdx >= 0 && nextIdx < sortedFields.length) {
1940
+ setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
1941
+ }
1942
+ }
1943
+ },
1944
+ onClick: (e) => e.stopPropagation()
1945
+ }
1946
+ ),
1947
+ isSingleSel && f.type === "checkbox" && /* @__PURE__ */ jsx4("label", { className: "field-list-checkbox", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx4(
1948
+ "input",
1949
+ {
1950
+ type: "checkbox",
1951
+ checked: f.value === "true",
1952
+ onChange: (e) => handleFieldUpdate(f.id, { value: e.target.checked ? "true" : "" }),
1953
+ onKeyDown: (e) => {
1954
+ if (e.key === "Tab") {
1955
+ e.preventDefault();
1956
+ const dir = e.shiftKey ? -1 : 1;
1957
+ const nextIdx = idx + dir;
1958
+ if (nextIdx >= 0 && nextIdx < sortedFields.length) {
1959
+ setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+ ) })
1965
+ ]
1966
+ },
1967
+ f.id
1968
+ );
1969
+ }) }) })
1304
1970
  ] }),
1305
1971
  /* @__PURE__ */ jsxs4("div", { className: "powered-by", children: [
1306
1972
  "Powered by ",
@@ -1313,7 +1979,7 @@ function DesignerView({
1313
1979
  }
1314
1980
 
1315
1981
  // src/components/pdf-builder/SignerView.tsx
1316
- import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
1982
+ import { useState as useState7, useCallback as useCallback5, useEffect as useEffect3, useRef as useRef5 } from "react";
1317
1983
 
1318
1984
  // src/utils/pdfFiller.ts
1319
1985
  import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
@@ -1450,7 +2116,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
1450
2116
  }
1451
2117
 
1452
2118
  // src/components/pdf-builder/FieldNavigator.tsx
1453
- import { useState as useState4 } from "react";
2119
+ import { useState as useState6 } from "react";
1454
2120
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1455
2121
  function isFieldFilled(f) {
1456
2122
  if (!f.required) return true;
@@ -1465,7 +2131,7 @@ function FieldNavigator({
1465
2131
  onComplete,
1466
2132
  completeLabel = "Complete"
1467
2133
  }) {
1468
- const [showIncomplete, setShowIncomplete] = useState4(false);
2134
+ const [showIncomplete, setShowIncomplete] = useState6(false);
1469
2135
  const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
1470
2136
  const hasPrev = currentIndex > 0;
1471
2137
  const hasNext = currentIndex < fields.length - 1;
@@ -1630,7 +2296,7 @@ function resolveAllFormulas(fields, customTransforms) {
1630
2296
  }
1631
2297
 
1632
2298
  // src/components/pdf-builder/SignerView.tsx
1633
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2299
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1634
2300
  function SignerView({
1635
2301
  apiKey,
1636
2302
  initialPdfUrl,
@@ -1641,7 +2307,12 @@ function SignerView({
1641
2307
  initialValues,
1642
2308
  submitLabel,
1643
2309
  signerOrder: signerOrderProp,
1644
- transforms
2310
+ transforms,
2311
+ onChange,
2312
+ onSignerComplete,
2313
+ includeAuditTrail,
2314
+ exportFormat,
2315
+ onExport
1645
2316
  } = {}) {
1646
2317
  if (!isValidApiKey(apiKey)) {
1647
2318
  return /* @__PURE__ */ jsx6("div", { className: "signer-layout", children: /* @__PURE__ */ jsxs6("div", { className: "empty-state", children: [
@@ -1653,23 +2324,24 @@ function SignerView({
1653
2324
  ] })
1654
2325
  ] }) });
1655
2326
  }
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(() => {
2327
+ const [pages, setPages] = useState7([]);
2328
+ const [fields, setFields] = useState7([]);
2329
+ const [selectedFieldId, setSelectedFieldId] = useState7(null);
2330
+ const [signerRoles, setSignerRoles] = useState7([]);
2331
+ const [currentSignerIndex, setCurrentSignerIndex] = useState7(() => {
1661
2332
  if (initialSigner && signerOrderProp) {
1662
2333
  const idx = signerOrderProp.indexOf(initialSigner);
1663
2334
  return idx >= 0 ? idx : 0;
1664
2335
  }
1665
2336
  return 0;
1666
2337
  });
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);
2338
+ const initializedRef = useRef5(false);
2339
+ const auditLogRef = useRef5([]);
2340
+ const [loading, setLoading] = useState7(false);
2341
+ const [submitting, setSubmitting] = useState7(false);
2342
+ const [pdfSource, setPdfSource] = useState7(null);
2343
+ const [callbackUrl, setCallbackUrl] = useState7(initialCallbackUrl || "");
2344
+ const containerRef = useRef5(null);
1673
2345
  const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
1674
2346
  const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
1675
2347
  const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
@@ -1759,7 +2431,7 @@ function SignerView({
1759
2431
  window.addEventListener("message", handleMessage);
1760
2432
  return () => window.removeEventListener("message", handleMessage);
1761
2433
  }, [initialTemplate, initialPdfUrl]);
1762
- const loadPdf = useCallback4(async (source) => {
2434
+ const loadPdf = useCallback5(async (source) => {
1763
2435
  setLoading(true);
1764
2436
  try {
1765
2437
  const rendered = await renderPdfPages(source);
@@ -1779,13 +2451,22 @@ function SignerView({
1779
2451
  });
1780
2452
  const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
1781
2453
  const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
1782
- const handleFieldUpdate = useCallback4((id, value) => {
2454
+ const handleFieldUpdate = useCallback5((id, value) => {
1783
2455
  setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
1784
2456
  }, []);
1785
- const handleFieldPropertyUpdate = useCallback4((id, updates) => {
2457
+ const handleFieldPropertyUpdate = useCallback5((id, updates) => {
1786
2458
  setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
1787
2459
  }, []);
1788
- const handleNavigate = useCallback4((fieldId) => {
2460
+ const fieldValuesKey = fields.map((f) => `${f.id}:${f.value}`).join("|");
2461
+ useEffect3(() => {
2462
+ if (!onChange) return;
2463
+ const values = {};
2464
+ fields.forEach((f) => {
2465
+ if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
2466
+ });
2467
+ onChange(values);
2468
+ }, [fieldValuesKey]);
2469
+ const handleNavigate = useCallback5((fieldId) => {
1789
2470
  setSelectedFieldId(fieldId);
1790
2471
  const field = fields.find((f) => f.id === fieldId);
1791
2472
  if (field && containerRef.current) {
@@ -1798,10 +2479,23 @@ function SignerView({
1798
2479
  const allRequiredFilled = editableFields.every((f) => {
1799
2480
  if (!f.required) return true;
1800
2481
  if (f.type === "checkbox") return true;
1801
- return !!f.value;
2482
+ if (!f.value) return false;
2483
+ if (f.minLength && f.value.length < f.minLength) return false;
2484
+ return true;
1802
2485
  });
1803
- const handleAdvanceOrSubmit = useCallback4(async () => {
2486
+ const getFieldValues = useCallback5(() => {
2487
+ const values = {};
2488
+ fields.forEach((f) => {
2489
+ if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
2490
+ });
2491
+ return values;
2492
+ }, [fields]);
2493
+ const handleAdvanceOrSubmit = useCallback5(async () => {
1804
2494
  if (!allRequiredFilled) return;
2495
+ auditLogRef.current.push({ signer, completedAt: (/* @__PURE__ */ new Date()).toISOString() });
2496
+ if (onSignerComplete) {
2497
+ onSignerComplete(signer, getFieldValues());
2498
+ }
1805
2499
  if (!isLastSigner) {
1806
2500
  setCurrentSignerIndex((prev) => prev + 1);
1807
2501
  setSelectedFieldId(null);
@@ -1815,8 +2509,40 @@ function SignerView({
1815
2509
  setSubmitting(true);
1816
2510
  try {
1817
2511
  const finalFields = resolveAllFormulas(fields, transforms);
1818
- const pdfBytes = await generateFilledPdf(pdfSource, finalFields);
2512
+ let pdfBytes;
2513
+ if (includeAuditTrail) {
2514
+ const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
2515
+ const basePdf = await generateFilledPdf(pdfSource, finalFields);
2516
+ const pdfDoc = await PDFDocument2.load(basePdf);
2517
+ const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
2518
+ const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
2519
+ const auditPage = pdfDoc.addPage([612, 792]);
2520
+ let cy = 742;
2521
+ auditPage.drawText("Signing Audit Trail", { x: 50, y: cy, size: 18, font: boldFont, color: rgb2(0, 0, 0) });
2522
+ cy -= 30;
2523
+ auditPage.drawText(`Document completed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, { x: 50, y: cy, size: 10, font, color: rgb2(0.4, 0.4, 0.4) });
2524
+ cy -= 30;
2525
+ for (const entry of auditLogRef.current) {
2526
+ auditPage.drawText(`${entry.signer}`, { x: 50, y: cy, size: 12, font: boldFont, color: rgb2(0, 0, 0) });
2527
+ auditPage.drawText(`Completed: ${new Date(entry.completedAt).toLocaleString()}`, { x: 200, y: cy, size: 10, font, color: rgb2(0.3, 0.3, 0.3) });
2528
+ cy -= 22;
2529
+ }
2530
+ pdfBytes = await pdfDoc.save();
2531
+ } else {
2532
+ pdfBytes = await generateFilledPdf(pdfSource, finalFields);
2533
+ }
1819
2534
  const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
2535
+ if (onExport && exportFormat) {
2536
+ const values = getFieldValues();
2537
+ if (exportFormat === "json") {
2538
+ onExport(JSON.stringify(values, null, 2), "json");
2539
+ } else {
2540
+ const headers = Object.keys(values);
2541
+ const row = headers.map((h) => `"${(values[h] || "").replace(/"/g, '""')}"`);
2542
+ onExport(`${headers.join(",")}
2543
+ ${row.join(",")}`, "csv");
2544
+ }
2545
+ }
1820
2546
  if (callbackUrl) {
1821
2547
  await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
1822
2548
  }
@@ -1832,8 +2558,8 @@ function SignerView({
1832
2558
  } finally {
1833
2559
  setSubmitting(false);
1834
2560
  }
1835
- }, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner]);
1836
- const renderFieldContent = useCallback4((field) => {
2561
+ }, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner, signer, onSignerComplete, includeAuditTrail, exportFormat, onExport, getFieldValues, transforms]);
2562
+ const renderFieldContent = useCallback5((field) => {
1837
2563
  if (field.type === "blackout" || field.type === "whiteout") {
1838
2564
  return null;
1839
2565
  }
@@ -1970,7 +2696,7 @@ function SignerView({
1970
2696
  initialValue: selectedField.value
1971
2697
  }
1972
2698
  ),
1973
- (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment2, { children: [
2699
+ (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1974
2700
  selectedField.type === "dropdown" || selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ jsxs6(
1975
2701
  "select",
1976
2702
  {
@@ -2051,7 +2777,7 @@ function SignerView({
2051
2777
  }
2052
2778
 
2053
2779
  // src/components/pdf-builder/SignerRoleSelector.tsx
2054
- import { useState as useState6 } from "react";
2780
+ import { useState as useState8 } from "react";
2055
2781
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2056
2782
  function SignerRoleSelector({
2057
2783
  roles,
@@ -2060,8 +2786,8 @@ function SignerRoleSelector({
2060
2786
  onAddRole,
2061
2787
  onRemoveRole
2062
2788
  }) {
2063
- const [isAdding, setIsAdding] = useState6(false);
2064
- const [newRoleName, setNewRoleName] = useState6("");
2789
+ const [isAdding, setIsAdding] = useState8(false);
2790
+ const [newRoleName, setNewRoleName] = useState8("");
2065
2791
  const handleAdd = () => {
2066
2792
  if (newRoleName.trim()) {
2067
2793
  onAddRole(newRoleName.trim());