@unlev/exeq 0.2.0 → 0.2.2

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.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  declare function generateId(): string;
4
- type FieldType = 'text' | 'signature' | 'signed-date' | 'checkbox' | 'initials' | 'blackout' | 'whiteout';
4
+ type FieldType = 'text' | 'dropdown' | 'signature' | 'signed-date' | 'checkbox' | 'initials' | 'blackout' | 'whiteout';
5
5
  type TextSubtype = 'freeform' | 'number' | 'date' | 'email' | 'phone';
6
6
  interface FormField {
7
7
  id: string;
@@ -112,17 +112,19 @@ declare function renderPdfPages(source: string | ArrayBuffer, scale?: number): P
112
112
  interface PdfViewerProps {
113
113
  pages: RenderedPage[];
114
114
  fields: FormField[];
115
- selectedFieldId: string | null;
116
- onSelectField: (id: string | null) => void;
115
+ selectedFieldIds: Set<string>;
116
+ onSelectField: (id: string | null, e?: React.MouseEvent) => void;
117
117
  onFieldMove?: (id: string, page: number, x: number, y: number) => void;
118
118
  onFieldResize?: (id: string, width: number, height: number) => void;
119
119
  onPageClick?: (page: number, x: number, y: number) => void;
120
120
  onDropField?: (page: number, x: number, y: number, fieldType: FieldType) => void;
121
+ /** Move multiple fields as a group. Called with dx/dy in percentage units. */
122
+ onGroupMove?: (ids: string[], dx: number, dy: number) => void;
121
123
  mode: 'designer' | 'signer';
122
124
  currentSigner?: string;
123
125
  renderFieldContent?: (field: FormField) => React.ReactNode;
124
126
  }
125
- declare function PdfViewer({ pages, fields, selectedFieldId, onSelectField, onFieldMove, onFieldResize, onPageClick, onDropField, mode, currentSigner, renderFieldContent, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
127
+ declare function PdfViewer({ pages, fields, selectedFieldIds, onSelectField, onFieldMove, onFieldResize, onPageClick, onDropField, onGroupMove, mode, currentSigner, renderFieldContent, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
126
128
 
127
129
  interface SignatureCanvasProps {
128
130
  width?: number;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  declare function generateId(): string;
4
- type FieldType = 'text' | 'signature' | 'signed-date' | 'checkbox' | 'initials' | 'blackout' | 'whiteout';
4
+ type FieldType = 'text' | 'dropdown' | 'signature' | 'signed-date' | 'checkbox' | 'initials' | 'blackout' | 'whiteout';
5
5
  type TextSubtype = 'freeform' | 'number' | 'date' | 'email' | 'phone';
6
6
  interface FormField {
7
7
  id: string;
@@ -112,17 +112,19 @@ declare function renderPdfPages(source: string | ArrayBuffer, scale?: number): P
112
112
  interface PdfViewerProps {
113
113
  pages: RenderedPage[];
114
114
  fields: FormField[];
115
- selectedFieldId: string | null;
116
- onSelectField: (id: string | null) => void;
115
+ selectedFieldIds: Set<string>;
116
+ onSelectField: (id: string | null, e?: React.MouseEvent) => void;
117
117
  onFieldMove?: (id: string, page: number, x: number, y: number) => void;
118
118
  onFieldResize?: (id: string, width: number, height: number) => void;
119
119
  onPageClick?: (page: number, x: number, y: number) => void;
120
120
  onDropField?: (page: number, x: number, y: number, fieldType: FieldType) => void;
121
+ /** Move multiple fields as a group. Called with dx/dy in percentage units. */
122
+ onGroupMove?: (ids: string[], dx: number, dy: number) => void;
121
123
  mode: 'designer' | 'signer';
122
124
  currentSigner?: string;
123
125
  renderFieldContent?: (field: FormField) => React.ReactNode;
124
126
  }
125
- declare function PdfViewer({ pages, fields, selectedFieldId, onSelectField, onFieldMove, onFieldResize, onPageClick, onDropField, mode, currentSigner, renderFieldContent, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
127
+ declare function PdfViewer({ pages, fields, selectedFieldIds, onSelectField, onFieldMove, onFieldResize, onPageClick, onDropField, onGroupMove, mode, currentSigner, renderFieldContent, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
126
128
 
127
129
  interface SignatureCanvasProps {
128
130
  width?: number;
package/dist/index.js CHANGED
@@ -95,6 +95,12 @@ var FIELD_DEFAULTS = {
95
95
  placeholder: "Enter text",
96
96
  textSubtype: "freeform"
97
97
  },
98
+ dropdown: {
99
+ width: 20,
100
+ height: 3,
101
+ fontSize: 12,
102
+ placeholder: "Select..."
103
+ },
98
104
  signature: {
99
105
  width: 20,
100
106
  height: 6,
@@ -134,6 +140,7 @@ var FIELD_DEFAULTS = {
134
140
  };
135
141
  var TYPE_LABELS = {
136
142
  text: "Text Field",
143
+ dropdown: "Dropdown",
137
144
  signature: "Signature",
138
145
  "signed-date": "Signed Date",
139
146
  checkbox: "Checkbox",
@@ -205,12 +212,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
205
212
  function PdfViewer({
206
213
  pages,
207
214
  fields,
208
- selectedFieldId,
215
+ selectedFieldIds,
209
216
  onSelectField,
210
217
  onFieldMove,
211
218
  onFieldResize,
212
219
  onPageClick,
213
220
  onDropField,
221
+ onGroupMove,
214
222
  mode,
215
223
  currentSigner,
216
224
  renderFieldContent
@@ -268,10 +276,13 @@ function PdfViewer({
268
276
  FieldOverlayItem,
269
277
  {
270
278
  field,
271
- isSelected: field.id === selectedFieldId,
272
- onSelect: () => onSelectField(field.id),
279
+ isSelected: selectedFieldIds.has(field.id),
280
+ isMultiSelected: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id),
281
+ onSelect: (e) => onSelectField(field.id, e),
273
282
  onMove: onFieldMove,
274
283
  onResize: onFieldResize,
284
+ onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
285
+ selectedIds: selectedFieldIds,
275
286
  mode,
276
287
  currentSigner,
277
288
  renderContent: renderFieldContent
@@ -287,9 +298,12 @@ function PdfViewer({
287
298
  function FieldOverlayItem({
288
299
  field,
289
300
  isSelected,
301
+ isMultiSelected,
290
302
  onSelect,
291
303
  onMove,
292
304
  onResize,
305
+ onGroupMove,
306
+ selectedIds,
293
307
  mode,
294
308
  currentSigner,
295
309
  renderContent
@@ -306,7 +320,7 @@ function FieldOverlayItem({
306
320
  if (mode !== "designer" || !onMove) return;
307
321
  e.preventDefault();
308
322
  e.stopPropagation();
309
- onSelect();
323
+ onSelect(e);
310
324
  const pageEl = overlayRef.current?.closest(".pdf-page");
311
325
  if (!pageEl) return;
312
326
  dragStartRef.current = {
@@ -315,14 +329,20 @@ function FieldOverlayItem({
315
329
  fieldX: field.x,
316
330
  fieldY: field.y
317
331
  };
318
- const handleMouseMove = (e2) => {
332
+ const handleMouseMove = (ev) => {
319
333
  if (!dragStartRef.current) return;
320
334
  const rect = pageEl.getBoundingClientRect();
321
- const dx = (e2.clientX - dragStartRef.current.startX) / rect.width * 100;
322
- const dy = (e2.clientY - dragStartRef.current.startY) / rect.height * 100;
323
- const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
324
- const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
325
- onMove(field.id, field.page, newX, newY);
335
+ const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
336
+ const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
337
+ if (isMultiSelected && onGroupMove) {
338
+ onGroupMove(Array.from(selectedIds), dx, dy);
339
+ dragStartRef.current.startX = ev.clientX;
340
+ dragStartRef.current.startY = ev.clientY;
341
+ } else {
342
+ const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
343
+ const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
344
+ onMove(field.id, field.page, newX, newY);
345
+ }
326
346
  };
327
347
  const handleMouseUp = () => {
328
348
  dragStartRef.current = null;
@@ -331,7 +351,7 @@ function FieldOverlayItem({
331
351
  };
332
352
  window.addEventListener("mousemove", handleMouseMove);
333
353
  window.addEventListener("mouseup", handleMouseUp);
334
- }, [field, mode, onMove, onSelect]);
354
+ }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
335
355
  const handleResizeMouseDown = (0, import_react.useCallback)((e) => {
336
356
  if (mode !== "designer" || !onResize) return;
337
357
  e.preventDefault();
@@ -379,7 +399,7 @@ function FieldOverlayItem({
379
399
  },
380
400
  onClick: (e) => {
381
401
  e.stopPropagation();
382
- onSelect();
402
+ onSelect(e);
383
403
  },
384
404
  onMouseDown: handleMouseDown,
385
405
  children: [
@@ -407,8 +427,8 @@ var INK_COLORS = [
407
427
  function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
408
428
  const color = getSignerColor(field.assignee);
409
429
  const isRedactField = field.type === "blackout" || field.type === "whiteout";
410
- const isTextField = field.type === "text" || field.type === "signed-date";
411
- const showInkColor = field.type === "text" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
430
+ const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
431
+ const showInkColor = field.type === "text" || field.type === "dropdown" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
412
432
  const [newOption, setNewOption] = (0, import_react2.useState)("");
413
433
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
414
434
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
@@ -435,6 +455,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
435
455
  onChange: (e) => onUpdate(field.id, { type: e.target.value }),
436
456
  children: [
437
457
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "text", children: "Text" }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "dropdown", children: "Dropdown" }),
438
459
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signature", children: "Signature" }),
439
460
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signed-date", children: "Signed Date" }),
440
461
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "checkbox", children: "Checkbox" }),
@@ -586,8 +607,8 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
586
607
  c.value
587
608
  )) })
588
609
  ] }),
589
- field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
590
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Predefined Options" }),
610
+ (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
591
612
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-options-list", children: [
592
613
  (field.options || []).map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-item", children: [
593
614
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: opt }),
@@ -633,7 +654,8 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
633
654
  )
634
655
  ] })
635
656
  ] }),
636
- (field.options?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
657
+ field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
658
+ field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
637
659
  ] })
638
660
  ] });
639
661
  }
@@ -782,6 +804,7 @@ function isValidApiKey(key) {
782
804
  var import_jsx_runtime4 = require("react/jsx-runtime");
783
805
  var FIELD_TYPE_META = [
784
806
  { type: "text", label: "Text", icon: "T" },
807
+ { type: "dropdown", label: "Dropdown", icon: "\u25BE" },
785
808
  { type: "signature", label: "Signature", icon: "\u270D" },
786
809
  { type: "signed-date", label: "Date", icon: "\u{1F4C5}" },
787
810
  { type: "checkbox", label: "Checkbox", icon: "\u2611" },
@@ -809,7 +832,7 @@ function DesignerView({
809
832
  }
810
833
  const [pages, setPages] = (0, import_react4.useState)([]);
811
834
  const [fields, setFields] = (0, import_react4.useState)(initialTemplate?.fields ?? []);
812
- const [selectedFieldId, setSelectedFieldId] = (0, import_react4.useState)(null);
835
+ const [selectedFieldIds, setSelectedFieldIds] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
813
836
  const [signerRoles, setSignerRoles] = (0, import_react4.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
814
837
  const [activeRole, setActiveRole] = (0, import_react4.useState)("Sender");
815
838
  const [activeFieldType, setActiveFieldType] = (0, import_react4.useState)("text");
@@ -820,7 +843,7 @@ function DesignerView({
820
843
  const [newRoleName, setNewRoleName] = (0, import_react4.useState)("");
821
844
  const [draggingFieldType, setDraggingFieldType] = (0, import_react4.useState)(null);
822
845
  const [panelWidth, setPanelWidth] = (0, import_react4.useState)(380);
823
- const [clipboardField, setClipboardField] = (0, import_react4.useState)(null);
846
+ const [clipboardFields, setClipboardFields] = (0, import_react4.useState)([]);
824
847
  const dragGhostRef = (0, import_react4.useRef)(null);
825
848
  const resizingRef = (0, import_react4.useRef)(false);
826
849
  const lastStylesRef = (0, import_react4.useRef)({});
@@ -874,7 +897,7 @@ function DesignerView({
874
897
  const sticky = lastStylesRef.current[activeFieldType];
875
898
  const styledField = sticky ? { ...field, ...sticky } : field;
876
899
  setFields((prev) => [...prev, styledField]);
877
- setSelectedFieldId(styledField.id);
900
+ setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
878
901
  setRightTab("properties");
879
902
  }, [activeFieldType, activeRole, fields]);
880
903
  const handleFieldMove = (0, import_react4.useCallback)((id, page, x, y) => {
@@ -912,8 +935,54 @@ function DesignerView({
912
935
  }, []);
913
936
  const handleFieldDelete = (0, import_react4.useCallback)((id) => {
914
937
  setFields((prev) => prev.filter((f) => f.id !== id));
915
- if (selectedFieldId === id) setSelectedFieldId(null);
916
- }, [selectedFieldId]);
938
+ setSelectedFieldIds((prev) => {
939
+ const next = new Set(prev);
940
+ next.delete(id);
941
+ return next;
942
+ });
943
+ }, []);
944
+ const handleSelectField = (0, import_react4.useCallback)((id, e) => {
945
+ if (!id) {
946
+ setSelectedFieldIds(/* @__PURE__ */ new Set());
947
+ return;
948
+ }
949
+ if (e && (e.metaKey || e.ctrlKey)) {
950
+ setSelectedFieldIds((prev) => {
951
+ const next = new Set(prev);
952
+ if (next.has(id)) next.delete(id);
953
+ else next.add(id);
954
+ return next;
955
+ });
956
+ } else if (e && e.shiftKey && selectedFieldIds.size > 0) {
957
+ const lastId = Array.from(selectedFieldIds).pop();
958
+ const sorted = sortedFields.map((f) => f.id);
959
+ const a = sorted.indexOf(lastId);
960
+ const b = sorted.indexOf(id);
961
+ if (a >= 0 && b >= 0) {
962
+ const start = Math.min(a, b);
963
+ const end = Math.max(a, b);
964
+ const range = sorted.slice(start, end + 1);
965
+ setSelectedFieldIds((prev) => {
966
+ const next = new Set(prev);
967
+ range.forEach((fid) => next.add(fid));
968
+ return next;
969
+ });
970
+ } else {
971
+ setSelectedFieldIds(/* @__PURE__ */ new Set([id]));
972
+ }
973
+ } else {
974
+ setSelectedFieldIds(/* @__PURE__ */ new Set([id]));
975
+ }
976
+ setRightTab("properties");
977
+ }, [selectedFieldIds]);
978
+ const handleGroupMove = (0, import_react4.useCallback)((ids, dx, dy) => {
979
+ setFields((prev) => prev.map((f) => {
980
+ if (!ids.includes(f.id)) return f;
981
+ const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
982
+ const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
983
+ return { ...f, x: newX, y: newY };
984
+ }));
985
+ }, []);
917
986
  const handleAddRole = (0, import_react4.useCallback)(() => {
918
987
  const name = newRoleName.trim();
919
988
  if (name && !signerRoles.includes(name)) {
@@ -976,56 +1045,58 @@ function DesignerView({
976
1045
  const sticky = lastStylesRef.current[fieldType];
977
1046
  const styledField = sticky ? { ...field, ...sticky } : field;
978
1047
  setFields((prev) => [...prev, styledField]);
979
- setSelectedFieldId(styledField.id);
1048
+ setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
980
1049
  setRightTab("properties");
981
1050
  }, [activeRole, fields]);
982
1051
  (0, import_react4.useEffect)(() => {
983
1052
  const handleKeyDown = (e) => {
984
1053
  if (e.key !== "Delete" && e.key !== "Backspace") return;
985
- if (!selectedFieldId) return;
1054
+ if (selectedFieldIds.size === 0) return;
986
1055
  const tag = (document.activeElement?.tagName || "").toLowerCase();
987
1056
  if (tag === "input" || tag === "textarea" || tag === "select") return;
988
1057
  if (document.activeElement?.isContentEditable) return;
989
1058
  e.preventDefault();
990
- if (window.confirm("Delete this field?")) {
991
- handleFieldDelete(selectedFieldId);
1059
+ const count = selectedFieldIds.size;
1060
+ if (window.confirm(`Delete ${count > 1 ? `${count} fields` : "this field"}?`)) {
1061
+ setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id)));
1062
+ setSelectedFieldIds(/* @__PURE__ */ new Set());
992
1063
  }
993
1064
  };
994
1065
  window.addEventListener("keydown", handleKeyDown);
995
1066
  return () => window.removeEventListener("keydown", handleKeyDown);
996
- }, [selectedFieldId, handleFieldDelete]);
1067
+ }, [selectedFieldIds]);
997
1068
  (0, import_react4.useEffect)(() => {
998
1069
  const handleKeyDown = (e) => {
999
1070
  const tag = (document.activeElement?.tagName || "").toLowerCase();
1000
1071
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1001
1072
  if (document.activeElement?.isContentEditable) return;
1002
1073
  const isMod = e.metaKey || e.ctrlKey;
1003
- if (isMod && e.key === "c" && selectedFieldId) {
1004
- const field = fields.find((f) => f.id === selectedFieldId);
1005
- if (field) {
1074
+ if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
1075
+ const selected = fields.filter((f) => selectedFieldIds.has(f.id));
1076
+ if (selected.length > 0) {
1006
1077
  e.preventDefault();
1007
- setClipboardField(field);
1078
+ setClipboardFields(selected);
1008
1079
  }
1009
1080
  }
1010
- if (isMod && e.key === "v" && clipboardField) {
1081
+ if (isMod && e.key === "v" && clipboardFields.length > 0) {
1011
1082
  e.preventDefault();
1012
- const existingLabels = fields.map((f) => f.label);
1013
- const newField = {
1014
- ...clipboardField,
1015
- id: generateId(),
1016
- label: uniqueLabel(clipboardField.label, existingLabels),
1017
- x: clipboardField.x + 2,
1018
- y: clipboardField.y + 2,
1019
- value: ""
1020
- };
1021
- setFields((prev) => [...prev, newField]);
1022
- setSelectedFieldId(newField.id);
1083
+ let existingLabels = fields.map((f) => f.label);
1084
+ const newIds = /* @__PURE__ */ new Set();
1085
+ const newFields = clipboardFields.map((cf) => {
1086
+ const label = uniqueLabel(cf.label, existingLabels);
1087
+ existingLabels.push(label);
1088
+ const id = generateId();
1089
+ newIds.add(id);
1090
+ return { ...cf, id, label, x: cf.x + 2, y: cf.y + 2, value: "" };
1091
+ });
1092
+ setFields((prev) => [...prev, ...newFields]);
1093
+ setSelectedFieldIds(newIds);
1023
1094
  setRightTab("properties");
1024
1095
  }
1025
1096
  };
1026
1097
  window.addEventListener("keydown", handleKeyDown);
1027
1098
  return () => window.removeEventListener("keydown", handleKeyDown);
1028
- }, [selectedFieldId, fields, clipboardField]);
1099
+ }, [selectedFieldIds, fields, clipboardFields]);
1029
1100
  const handleResizeStart = (0, import_react4.useCallback)((e) => {
1030
1101
  e.preventDefault();
1031
1102
  resizingRef.current = true;
@@ -1050,7 +1121,7 @@ function DesignerView({
1050
1121
  if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
1051
1122
  return a.x - b.x;
1052
1123
  });
1053
- const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
1124
+ const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
1054
1125
  const renderFieldContent = (0, import_react4.useCallback)((field) => {
1055
1126
  if (field.type === "blackout" || field.type === "whiteout") {
1056
1127
  return null;
@@ -1061,15 +1132,22 @@ function DesignerView({
1061
1132
  }
1062
1133
  }
1063
1134
  const inkColor = field.inkColor || "#000000";
1135
+ const cssFontFamily = field.fontFamily === "Courier" ? '"Courier New", Courier, monospace' : field.fontFamily === "TimesRoman" ? '"Times New Roman", Times, serif' : field.fontFamily === "Helvetica" ? "Helvetica, Arial, sans-serif" : void 0;
1064
1136
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1065
1137
  "div",
1066
1138
  {
1067
1139
  className: "field-overlay-placeholder",
1068
- style: { color: field.value ? inkColor : void 0, fontSize: `${field.fontSize * 0.6}px` },
1140
+ style: {
1141
+ color: field.value ? inkColor : void 0,
1142
+ fontSize: `${field.fontSize * 0.6}px`,
1143
+ fontFamily: cssFontFamily,
1144
+ letterSpacing: field.letterSpacing ? `${field.letterSpacing * 0.6}px` : void 0,
1145
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0
1146
+ },
1069
1147
  children: field.value || field.placeholder
1070
1148
  }
1071
1149
  );
1072
- }, []);
1150
+ }, [fields]);
1073
1151
  const headerButtons = pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1074
1152
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "header-btn header-btn-outline", children: [
1075
1153
  "Change PDF",
@@ -1163,13 +1241,11 @@ function DesignerView({
1163
1241
  {
1164
1242
  pages,
1165
1243
  fields,
1166
- selectedFieldId,
1167
- onSelectField: (id) => {
1168
- setSelectedFieldId(id);
1169
- if (id) setRightTab("properties");
1170
- },
1244
+ selectedFieldIds,
1245
+ onSelectField: handleSelectField,
1171
1246
  onFieldMove: handleFieldMove,
1172
1247
  onFieldResize: handleFieldResize,
1248
+ onGroupMove: handleGroupMove,
1173
1249
  onPageClick: handlePageClick,
1174
1250
  onDropField: handleDropOnPage,
1175
1251
  mode: "designer",
@@ -1206,7 +1282,13 @@ function DesignerView({
1206
1282
  )
1207
1283
  ] }),
1208
1284
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tab-content", children: [
1209
- rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1285
+ rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-empty", children: [
1286
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { children: [
1287
+ selectedFieldIds.size,
1288
+ " fields selected"
1289
+ ] }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Drag to move as a group. Press Delete to remove all. Cmd/Ctrl+C to copy." })
1291
+ ] }) : selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1210
1292
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1211
1293
  FieldPropertyPanel,
1212
1294
  {
@@ -1253,9 +1335,9 @@ function DesignerView({
1253
1335
  rightTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "No fields yet. Drag a field from the left palette onto the PDF, or click on the PDF to place one." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1254
1336
  "div",
1255
1337
  {
1256
- className: `field-list-item ${f.id === selectedFieldId ? "active" : ""}`,
1338
+ className: `field-list-item ${selectedFieldIds.has(f.id) ? "active" : ""}`,
1257
1339
  onClick: () => {
1258
- setSelectedFieldId(f.id);
1340
+ setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
1259
1341
  setRightTab("properties");
1260
1342
  },
1261
1343
  children: [
@@ -1850,9 +1932,10 @@ function SignerView({
1850
1932
  const fontStyle = {
1851
1933
  fontSize: `${field.fontSize * 0.6}px`,
1852
1934
  letterSpacing: field.letterSpacing ? `${field.letterSpacing * 0.6}px` : void 0,
1853
- fontFamily: field.fontFamily === "Courier" ? "monospace" : field.fontFamily === "TimesRoman" ? "serif" : void 0
1935
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
1936
+ fontFamily: field.fontFamily === "Courier" ? '"Courier New", Courier, monospace' : field.fontFamily === "TimesRoman" ? '"Times New Roman", Times, serif' : field.fontFamily === "Helvetica" ? "Helvetica, Arial, sans-serif" : void 0
1854
1937
  };
1855
- if (field.options && field.options.length > 0) {
1938
+ if (field.type === "dropdown" || field.options && field.options.length > 0) {
1856
1939
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1857
1940
  "select",
1858
1941
  {
@@ -1864,7 +1947,7 @@ function SignerView({
1864
1947
  style: fontStyle,
1865
1948
  children: [
1866
1949
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: field.placeholder || "Select..." }),
1867
- field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: opt, children: opt }, opt))
1950
+ (field.options || []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: opt, children: opt }, opt))
1868
1951
  ]
1869
1952
  }
1870
1953
  );
@@ -1911,8 +1994,8 @@ function SignerView({
1911
1994
  {
1912
1995
  pages,
1913
1996
  fields,
1914
- selectedFieldId,
1915
- onSelectField: setSelectedFieldId,
1997
+ selectedFieldIds: new Set(selectedFieldId ? [selectedFieldId] : []),
1998
+ onSelectField: (id) => setSelectedFieldId(id),
1916
1999
  mode: "signer",
1917
2000
  currentSigner: signer,
1918
2001
  renderFieldContent
@@ -1944,8 +2027,8 @@ function SignerView({
1944
2027
  initialValue: selectedField.value
1945
2028
  }
1946
2029
  ),
1947
- selectedField.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1948
- selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2030
+ (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2031
+ selectedField.type === "dropdown" || selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1949
2032
  "select",
1950
2033
  {
1951
2034
  value: selectedField.value,
@@ -1953,7 +2036,7 @@ function SignerView({
1953
2036
  className: "signer-text-input",
1954
2037
  children: [
1955
2038
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: selectedField.placeholder || "Select..." }),
1956
- selectedField.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: opt, children: opt }, opt))
2039
+ (selectedField.options || []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: opt, children: opt }, opt))
1957
2040
  ]
1958
2041
  }
1959
2042
  ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(