@unlev/exeq 0.2.1 → 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
@@ -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
@@ -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
@@ -212,12 +212,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
212
212
  function PdfViewer({
213
213
  pages,
214
214
  fields,
215
- selectedFieldId,
215
+ selectedFieldIds,
216
216
  onSelectField,
217
217
  onFieldMove,
218
218
  onFieldResize,
219
219
  onPageClick,
220
220
  onDropField,
221
+ onGroupMove,
221
222
  mode,
222
223
  currentSigner,
223
224
  renderFieldContent
@@ -275,10 +276,13 @@ function PdfViewer({
275
276
  FieldOverlayItem,
276
277
  {
277
278
  field,
278
- isSelected: field.id === selectedFieldId,
279
- 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),
280
282
  onMove: onFieldMove,
281
283
  onResize: onFieldResize,
284
+ onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
285
+ selectedIds: selectedFieldIds,
282
286
  mode,
283
287
  currentSigner,
284
288
  renderContent: renderFieldContent
@@ -294,9 +298,12 @@ function PdfViewer({
294
298
  function FieldOverlayItem({
295
299
  field,
296
300
  isSelected,
301
+ isMultiSelected,
297
302
  onSelect,
298
303
  onMove,
299
304
  onResize,
305
+ onGroupMove,
306
+ selectedIds,
300
307
  mode,
301
308
  currentSigner,
302
309
  renderContent
@@ -313,7 +320,7 @@ function FieldOverlayItem({
313
320
  if (mode !== "designer" || !onMove) return;
314
321
  e.preventDefault();
315
322
  e.stopPropagation();
316
- onSelect();
323
+ onSelect(e);
317
324
  const pageEl = overlayRef.current?.closest(".pdf-page");
318
325
  if (!pageEl) return;
319
326
  dragStartRef.current = {
@@ -322,14 +329,20 @@ function FieldOverlayItem({
322
329
  fieldX: field.x,
323
330
  fieldY: field.y
324
331
  };
325
- const handleMouseMove = (e2) => {
332
+ const handleMouseMove = (ev) => {
326
333
  if (!dragStartRef.current) return;
327
334
  const rect = pageEl.getBoundingClientRect();
328
- const dx = (e2.clientX - dragStartRef.current.startX) / rect.width * 100;
329
- const dy = (e2.clientY - dragStartRef.current.startY) / rect.height * 100;
330
- const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
331
- const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
332
- 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
+ }
333
346
  };
334
347
  const handleMouseUp = () => {
335
348
  dragStartRef.current = null;
@@ -338,7 +351,7 @@ function FieldOverlayItem({
338
351
  };
339
352
  window.addEventListener("mousemove", handleMouseMove);
340
353
  window.addEventListener("mouseup", handleMouseUp);
341
- }, [field, mode, onMove, onSelect]);
354
+ }, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
342
355
  const handleResizeMouseDown = (0, import_react.useCallback)((e) => {
343
356
  if (mode !== "designer" || !onResize) return;
344
357
  e.preventDefault();
@@ -386,7 +399,7 @@ function FieldOverlayItem({
386
399
  },
387
400
  onClick: (e) => {
388
401
  e.stopPropagation();
389
- onSelect();
402
+ onSelect(e);
390
403
  },
391
404
  onMouseDown: handleMouseDown,
392
405
  children: [
@@ -819,7 +832,7 @@ function DesignerView({
819
832
  }
820
833
  const [pages, setPages] = (0, import_react4.useState)([]);
821
834
  const [fields, setFields] = (0, import_react4.useState)(initialTemplate?.fields ?? []);
822
- const [selectedFieldId, setSelectedFieldId] = (0, import_react4.useState)(null);
835
+ const [selectedFieldIds, setSelectedFieldIds] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
823
836
  const [signerRoles, setSignerRoles] = (0, import_react4.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
824
837
  const [activeRole, setActiveRole] = (0, import_react4.useState)("Sender");
825
838
  const [activeFieldType, setActiveFieldType] = (0, import_react4.useState)("text");
@@ -830,7 +843,7 @@ function DesignerView({
830
843
  const [newRoleName, setNewRoleName] = (0, import_react4.useState)("");
831
844
  const [draggingFieldType, setDraggingFieldType] = (0, import_react4.useState)(null);
832
845
  const [panelWidth, setPanelWidth] = (0, import_react4.useState)(380);
833
- const [clipboardField, setClipboardField] = (0, import_react4.useState)(null);
846
+ const [clipboardFields, setClipboardFields] = (0, import_react4.useState)([]);
834
847
  const dragGhostRef = (0, import_react4.useRef)(null);
835
848
  const resizingRef = (0, import_react4.useRef)(false);
836
849
  const lastStylesRef = (0, import_react4.useRef)({});
@@ -884,7 +897,7 @@ function DesignerView({
884
897
  const sticky = lastStylesRef.current[activeFieldType];
885
898
  const styledField = sticky ? { ...field, ...sticky } : field;
886
899
  setFields((prev) => [...prev, styledField]);
887
- setSelectedFieldId(styledField.id);
900
+ setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
888
901
  setRightTab("properties");
889
902
  }, [activeFieldType, activeRole, fields]);
890
903
  const handleFieldMove = (0, import_react4.useCallback)((id, page, x, y) => {
@@ -922,8 +935,54 @@ function DesignerView({
922
935
  }, []);
923
936
  const handleFieldDelete = (0, import_react4.useCallback)((id) => {
924
937
  setFields((prev) => prev.filter((f) => f.id !== id));
925
- if (selectedFieldId === id) setSelectedFieldId(null);
926
- }, [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
+ }, []);
927
986
  const handleAddRole = (0, import_react4.useCallback)(() => {
928
987
  const name = newRoleName.trim();
929
988
  if (name && !signerRoles.includes(name)) {
@@ -986,56 +1045,58 @@ function DesignerView({
986
1045
  const sticky = lastStylesRef.current[fieldType];
987
1046
  const styledField = sticky ? { ...field, ...sticky } : field;
988
1047
  setFields((prev) => [...prev, styledField]);
989
- setSelectedFieldId(styledField.id);
1048
+ setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
990
1049
  setRightTab("properties");
991
1050
  }, [activeRole, fields]);
992
1051
  (0, import_react4.useEffect)(() => {
993
1052
  const handleKeyDown = (e) => {
994
1053
  if (e.key !== "Delete" && e.key !== "Backspace") return;
995
- if (!selectedFieldId) return;
1054
+ if (selectedFieldIds.size === 0) return;
996
1055
  const tag = (document.activeElement?.tagName || "").toLowerCase();
997
1056
  if (tag === "input" || tag === "textarea" || tag === "select") return;
998
1057
  if (document.activeElement?.isContentEditable) return;
999
1058
  e.preventDefault();
1000
- if (window.confirm("Delete this field?")) {
1001
- 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());
1002
1063
  }
1003
1064
  };
1004
1065
  window.addEventListener("keydown", handleKeyDown);
1005
1066
  return () => window.removeEventListener("keydown", handleKeyDown);
1006
- }, [selectedFieldId, handleFieldDelete]);
1067
+ }, [selectedFieldIds]);
1007
1068
  (0, import_react4.useEffect)(() => {
1008
1069
  const handleKeyDown = (e) => {
1009
1070
  const tag = (document.activeElement?.tagName || "").toLowerCase();
1010
1071
  if (tag === "input" || tag === "textarea" || tag === "select") return;
1011
1072
  if (document.activeElement?.isContentEditable) return;
1012
1073
  const isMod = e.metaKey || e.ctrlKey;
1013
- if (isMod && e.key === "c" && selectedFieldId) {
1014
- const field = fields.find((f) => f.id === selectedFieldId);
1015
- 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) {
1016
1077
  e.preventDefault();
1017
- setClipboardField(field);
1078
+ setClipboardFields(selected);
1018
1079
  }
1019
1080
  }
1020
- if (isMod && e.key === "v" && clipboardField) {
1081
+ if (isMod && e.key === "v" && clipboardFields.length > 0) {
1021
1082
  e.preventDefault();
1022
- const existingLabels = fields.map((f) => f.label);
1023
- const newField = {
1024
- ...clipboardField,
1025
- id: generateId(),
1026
- label: uniqueLabel(clipboardField.label, existingLabels),
1027
- x: clipboardField.x + 2,
1028
- y: clipboardField.y + 2,
1029
- value: ""
1030
- };
1031
- setFields((prev) => [...prev, newField]);
1032
- 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);
1033
1094
  setRightTab("properties");
1034
1095
  }
1035
1096
  };
1036
1097
  window.addEventListener("keydown", handleKeyDown);
1037
1098
  return () => window.removeEventListener("keydown", handleKeyDown);
1038
- }, [selectedFieldId, fields, clipboardField]);
1099
+ }, [selectedFieldIds, fields, clipboardFields]);
1039
1100
  const handleResizeStart = (0, import_react4.useCallback)((e) => {
1040
1101
  e.preventDefault();
1041
1102
  resizingRef.current = true;
@@ -1060,7 +1121,7 @@ function DesignerView({
1060
1121
  if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
1061
1122
  return a.x - b.x;
1062
1123
  });
1063
- 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;
1064
1125
  const renderFieldContent = (0, import_react4.useCallback)((field) => {
1065
1126
  if (field.type === "blackout" || field.type === "whiteout") {
1066
1127
  return null;
@@ -1180,13 +1241,11 @@ function DesignerView({
1180
1241
  {
1181
1242
  pages,
1182
1243
  fields,
1183
- selectedFieldId,
1184
- onSelectField: (id) => {
1185
- setSelectedFieldId(id);
1186
- if (id) setRightTab("properties");
1187
- },
1244
+ selectedFieldIds,
1245
+ onSelectField: handleSelectField,
1188
1246
  onFieldMove: handleFieldMove,
1189
1247
  onFieldResize: handleFieldResize,
1248
+ onGroupMove: handleGroupMove,
1190
1249
  onPageClick: handlePageClick,
1191
1250
  onDropField: handleDropOnPage,
1192
1251
  mode: "designer",
@@ -1223,7 +1282,13 @@ function DesignerView({
1223
1282
  )
1224
1283
  ] }),
1225
1284
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tab-content", children: [
1226
- 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: [
1227
1292
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1228
1293
  FieldPropertyPanel,
1229
1294
  {
@@ -1270,9 +1335,9 @@ function DesignerView({
1270
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)(
1271
1336
  "div",
1272
1337
  {
1273
- className: `field-list-item ${f.id === selectedFieldId ? "active" : ""}`,
1338
+ className: `field-list-item ${selectedFieldIds.has(f.id) ? "active" : ""}`,
1274
1339
  onClick: () => {
1275
- setSelectedFieldId(f.id);
1340
+ setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
1276
1341
  setRightTab("properties");
1277
1342
  },
1278
1343
  children: [
@@ -1929,8 +1994,8 @@ function SignerView({
1929
1994
  {
1930
1995
  pages,
1931
1996
  fields,
1932
- selectedFieldId,
1933
- onSelectField: setSelectedFieldId,
1997
+ selectedFieldIds: new Set(selectedFieldId ? [selectedFieldId] : []),
1998
+ onSelectField: (id) => setSelectedFieldId(id),
1934
1999
  mode: "signer",
1935
2000
  currentSigner: signer,
1936
2001
  renderFieldContent