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