@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.css +246 -3
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +1082 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1088 -362
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/pdf-builder/DesignerView.tsx
|
|
2
|
-
import { useState as
|
|
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
|
|
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 (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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" ? "
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
/* @__PURE__ */
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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-
|
|
428
|
-
/* @__PURE__ */ jsx2("
|
|
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
|
-
|
|
439
|
-
/* @__PURE__ */ jsx2("
|
|
440
|
-
/* @__PURE__ */
|
|
441
|
-
"
|
|
442
|
-
{
|
|
443
|
-
|
|
444
|
-
value:
|
|
445
|
-
|
|
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-
|
|
450
|
-
/* @__PURE__ */ jsx2("
|
|
451
|
-
/* @__PURE__ */
|
|
452
|
-
"
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
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:
|
|
468
|
-
|
|
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
|
-
|
|
815
|
+
hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
474
816
|
/* @__PURE__ */ jsx2("label", { children: "Font" }),
|
|
475
|
-
/* @__PURE__ */
|
|
817
|
+
/* @__PURE__ */ jsxs2(
|
|
476
818
|
"select",
|
|
477
819
|
{
|
|
478
|
-
value:
|
|
479
|
-
onChange: (e) => onUpdate(
|
|
480
|
-
children:
|
|
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
|
-
|
|
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:
|
|
493
|
-
|
|
837
|
+
value: sharedFontSize ?? "",
|
|
838
|
+
placeholder: "Mixed",
|
|
839
|
+
onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
|
|
494
840
|
}
|
|
495
841
|
)
|
|
496
842
|
] }),
|
|
497
|
-
|
|
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:
|
|
508
|
-
|
|
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:
|
|
522
|
-
|
|
868
|
+
value: sharedLineHeight ?? "",
|
|
869
|
+
placeholder: "Mixed",
|
|
870
|
+
onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
|
|
523
871
|
}
|
|
524
872
|
)
|
|
525
873
|
] })
|
|
526
874
|
] }),
|
|
527
|
-
|
|
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 ${
|
|
880
|
+
className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
|
|
546
881
|
style: { backgroundColor: c.value },
|
|
547
|
-
onClick: () => onUpdate(
|
|
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
|
|
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] =
|
|
633
|
-
const [currentPath, setCurrentPath] =
|
|
634
|
-
const [isEmpty, setIsEmpty] =
|
|
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] =
|
|
777
|
-
const
|
|
778
|
-
const [selectedFieldIds, setSelectedFieldIds] =
|
|
779
|
-
const [signerRoles, setSignerRoles] =
|
|
780
|
-
const [activeRole, setActiveRole] =
|
|
781
|
-
const [activeFieldType, setActiveFieldType] =
|
|
782
|
-
const [loading, setLoading] =
|
|
783
|
-
const [pdfSource, setPdfSource] =
|
|
784
|
-
const [rightTab, setRightTab] =
|
|
785
|
-
const [isAddingRole, setIsAddingRole] =
|
|
786
|
-
const [newRoleName, setNewRoleName] =
|
|
787
|
-
const [draggingFieldType, setDraggingFieldType] =
|
|
788
|
-
const [panelWidth, setPanelWidth] =
|
|
789
|
-
const [
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
const
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
922
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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: "
|
|
1203
|
-
/* @__PURE__ */
|
|
1204
|
-
|
|
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(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
1291
|
-
|
|
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
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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
|
|
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
|
|
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] =
|
|
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
|
|
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] =
|
|
1657
|
-
const [fields, setFields] =
|
|
1658
|
-
const [selectedFieldId, setSelectedFieldId] =
|
|
1659
|
-
const [signerRoles, setSignerRoles] =
|
|
1660
|
-
const [currentSignerIndex, setCurrentSignerIndex] =
|
|
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 =
|
|
1668
|
-
const
|
|
1669
|
-
const [
|
|
1670
|
-
const [
|
|
1671
|
-
const [
|
|
1672
|
-
const
|
|
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 =
|
|
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 =
|
|
2454
|
+
const handleFieldUpdate = useCallback5((id, value) => {
|
|
1783
2455
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1784
2456
|
}, []);
|
|
1785
|
-
const handleFieldPropertyUpdate =
|
|
2457
|
+
const handleFieldPropertyUpdate = useCallback5((id, updates) => {
|
|
1786
2458
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
|
|
1787
2459
|
}, []);
|
|
1788
|
-
const
|
|
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
|
-
|
|
2482
|
+
if (!f.value) return false;
|
|
2483
|
+
if (f.minLength && f.value.length < f.minLength) return false;
|
|
2484
|
+
return true;
|
|
1802
2485
|
});
|
|
1803
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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] =
|
|
2064
|
-
const [newRoleName, setNewRoleName] =
|
|
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());
|