@unlev/exeq 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -60,11 +60,17 @@ function getCssTextStyle(field) {
60
60
  const decorations = [];
61
61
  if (field.underline) decorations.push("underline");
62
62
  if (field.strikethrough) decorations.push("line-through");
63
+ const align = field.align ?? "left";
63
64
  return {
64
65
  fontFamily: getCssFontFamily(field.fontFamily),
65
66
  fontWeight: field.bold ? "bold" : void 0,
66
67
  fontStyle: field.italic ? "italic" : void 0,
67
- textDecorationLine: decorations.length ? decorations.join(" ") : void 0
68
+ textDecorationLine: decorations.length ? decorations.join(" ") : void 0,
69
+ textAlign: field.align,
70
+ // Hug the alignment edge — no padding on the trailing side — so text reaches
71
+ // the container edge (matches the PDF's 2pt offsets).
72
+ paddingLeft: align === "right" ? "0px" : "2px",
73
+ paddingRight: align === "left" ? "0px" : "2px"
68
74
  };
69
75
  }
70
76
  function sortFieldsByPosition(fields) {
@@ -735,6 +741,27 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
735
741
  /* @__PURE__ */ jsx2("button", { type: "button", className: `text-format-btn ${field.strikethrough ? "active" : ""}`, style: { textDecorationLine: "line-through" }, title: "Strikethrough", onClick: () => onUpdate(field.id, { strikethrough: !field.strikethrough }), children: "S" })
736
742
  ] })
737
743
  ] }),
744
+ isText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
745
+ /* @__PURE__ */ jsx2("label", { children: "Alignment" }),
746
+ /* @__PURE__ */ jsx2("div", { className: "text-format-buttons", children: [
747
+ ["left", "Left", "\u2AF7"],
748
+ ["center", "Center", "\u2261"],
749
+ ["right", "Right", "\u2AF8"]
750
+ ].map(([value, label, glyph]) => {
751
+ const active = (field.align || "left") === value;
752
+ return /* @__PURE__ */ jsx2(
753
+ "button",
754
+ {
755
+ type: "button",
756
+ className: `text-format-btn ${active ? "active" : ""}`,
757
+ title: `Align ${label}`,
758
+ onClick: () => onUpdate(field.id, { align: value === "left" ? void 0 : value }),
759
+ children: glyph
760
+ },
761
+ value
762
+ );
763
+ }) })
764
+ ] }),
738
765
  isText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
739
766
  /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
740
767
  /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
@@ -2158,6 +2185,20 @@ var BUILTIN_TRANSFORMS = {
2158
2185
  return parts[parts.length - 1] || "";
2159
2186
  },
2160
2187
  initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
2188
+ // Name parts. Handles "Last, First [Middle]" (comma form) and "First [Middle]
2189
+ // Last" (no comma). firstName → the given name(s); lastName → the surname.
2190
+ firstName: (v) => {
2191
+ const c = v.indexOf(",");
2192
+ if (c >= 0) return v.slice(c + 1).trim();
2193
+ const parts = v.trim().split(/\s+/).filter(Boolean);
2194
+ return parts.length > 1 ? parts.slice(0, -1).join(" ") : parts[0] || "";
2195
+ },
2196
+ lastName: (v) => {
2197
+ const c = v.indexOf(",");
2198
+ if (c >= 0) return v.slice(0, c).trim();
2199
+ const parts = v.trim().split(/\s+/).filter(Boolean);
2200
+ return parts.length > 1 ? parts[parts.length - 1] : parts[0] || "";
2201
+ },
2161
2202
  // Numeric / substring
2162
2203
  last4: (v) => v.slice(-4),
2163
2204
  last2: (v) => v.slice(-2),
@@ -2187,11 +2228,17 @@ function resolveFormula(formula, fields, customTransforms, values) {
2187
2228
  const transformName = pipeMatch ? pipeMatch[2].trim() : null;
2188
2229
  const lowerLabel = sourceLabel.toLowerCase();
2189
2230
  const sourceField = fields.find((f) => f.label.toLowerCase() === lowerLabel) || fields.find((f) => f.id === sourceLabel);
2190
- let rawValue = sourceField ? sourceField.value || "" : void 0;
2231
+ let rawValue;
2232
+ if (sourceField && sourceField.value) {
2233
+ rawValue = sourceField.value;
2234
+ }
2191
2235
  if (rawValue === void 0 && values) {
2192
2236
  const key = Object.keys(values).find((k) => k.toLowerCase() === lowerLabel);
2193
2237
  if (key !== void 0) rawValue = values[key] || "";
2194
2238
  }
2239
+ if (rawValue === void 0 && sourceField) {
2240
+ rawValue = sourceField.value || "";
2241
+ }
2195
2242
  if (rawValue === void 0 && (lowerLabel === "today" || lowerLabel === "now")) {
2196
2243
  rawValue = todayIso();
2197
2244
  }
@@ -2326,7 +2373,9 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2326
2373
  fieldHeightPt: h,
2327
2374
  measure: textWidthAtSize
2328
2375
  });
2329
- const textX = x + 2;
2376
+ const fullTextWidth = textWidthAtSize(field.value, fontSize);
2377
+ const pad = 2;
2378
+ const textX = field.align === "center" ? x + Math.max(pad, (w - fullTextWidth) / 2) : field.align === "right" ? x + Math.max(pad, w - fullTextWidth - pad) : x + pad;
2330
2379
  const baselineY = y + h * 0.3;
2331
2380
  if (spacing > 0) {
2332
2381
  let cx = textX;
@@ -2580,37 +2629,54 @@ function FieldNavigator({
2580
2629
  // src/components/pdf-builder/SignerView.tsx
2581
2630
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2582
2631
  var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
2583
- function shrinkToFit(el, maxPt, selfBox) {
2584
- if (!el) return;
2585
- const box = selfBox ? el : el.parentElement;
2586
- if (!box) return;
2632
+ var measureCtx = null;
2633
+ function fitFont(el, box, maxPt, text, shrink) {
2634
+ if (!shrink || typeof document === "undefined") {
2635
+ el.style.fontSize = `${maxPt}pt`;
2636
+ return;
2637
+ }
2638
+ if (!measureCtx) measureCtx = document.createElement("canvas").getContext("2d");
2639
+ const boxStyle = getComputedStyle(box);
2640
+ const padX = (parseFloat(boxStyle.paddingLeft) || 0) + (parseFloat(boxStyle.paddingRight) || 0);
2641
+ const avail = box.clientWidth - padX - 1;
2642
+ if (!text || !measureCtx || avail <= 0) {
2643
+ el.style.fontSize = `${maxPt}pt`;
2644
+ return;
2645
+ }
2646
+ const cs = getComputedStyle(el);
2647
+ const PX_PER_PT = 96 / 72;
2587
2648
  let pt = Math.max(1, maxPt);
2588
- el.style.fontSize = `${pt}pt`;
2589
- let guard = 0;
2590
- while (pt > 2 && guard++ < 400 && (el.scrollWidth > box.clientWidth + 0.5 || el.scrollHeight > box.clientHeight + 0.5)) {
2649
+ while (pt > 2) {
2650
+ measureCtx.font = `${cs.fontStyle} ${cs.fontWeight} ${pt * PX_PER_PT}px ${cs.fontFamily}`;
2651
+ if (measureCtx.measureText(text).width <= avail) break;
2591
2652
  pt -= 0.5;
2592
- el.style.fontSize = `${pt}pt`;
2593
2653
  }
2654
+ el.style.fontSize = `${pt}pt`;
2594
2655
  }
2595
2656
  function FitInput({
2596
2657
  maxPt,
2658
+ shrink,
2597
2659
  ...rest
2598
2660
  }) {
2599
2661
  const ref = useRef5(null);
2600
2662
  useIsoLayoutEffect(() => {
2601
- shrinkToFit(ref.current, maxPt, true);
2663
+ const el = ref.current;
2664
+ if (el) fitFont(el, el, maxPt, String(el.value ?? ""), shrink);
2602
2665
  });
2603
2666
  return /* @__PURE__ */ jsx6("input", { ref, ...rest });
2604
2667
  }
2605
2668
  function FitText({
2606
2669
  maxPt,
2670
+ shrink,
2607
2671
  className,
2608
2672
  style,
2609
2673
  children
2610
2674
  }) {
2611
2675
  const ref = useRef5(null);
2612
2676
  useIsoLayoutEffect(() => {
2613
- shrinkToFit(ref.current, maxPt, false);
2677
+ const el = ref.current;
2678
+ const box = el?.parentElement;
2679
+ if (el && box) fitFont(el, box, maxPt, el.textContent ?? "", shrink);
2614
2680
  });
2615
2681
  return /* @__PURE__ */ jsx6("div", { ref, className, style, children });
2616
2682
  }
@@ -2869,8 +2935,13 @@ ${row.join(",")}`, "csv");
2869
2935
  if (isRedactField(field)) {
2870
2936
  return null;
2871
2937
  }
2938
+ const textStyle = {
2939
+ letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
2940
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
2941
+ ...getCssTextStyle(field)
2942
+ };
2872
2943
  if (field.formula) {
2873
- return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, className: "field-overlay-value formula", style: getCssTextStyle(field), children: field.value || "..." });
2944
+ return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, shrink: !!field.autoShrink, className: "field-overlay-value formula", style: textStyle, children: field.value || "" });
2874
2945
  }
2875
2946
  const editable = field.assignee === signer;
2876
2947
  if (!editable) {
@@ -2880,7 +2951,7 @@ ${row.join(",")}`, "csv");
2880
2951
  if (field.type === "checkbox") {
2881
2952
  return /* @__PURE__ */ jsx6("div", { className: "field-checkbox-display readonly", children: field.value === "true" ? "\u2713" : "" });
2882
2953
  }
2883
- return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, className: "field-overlay-placeholder readonly", style: getCssTextStyle(field), children: field.value || field.placeholder });
2954
+ return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, shrink: !!field.autoShrink, className: "field-overlay-placeholder readonly", style: textStyle, children: field.value || field.placeholder });
2884
2955
  }
2885
2956
  if (isSignatureField(field)) {
2886
2957
  if (field.value) {
@@ -2902,13 +2973,8 @@ ${row.join(",")}`, "csv");
2902
2973
  );
2903
2974
  }
2904
2975
  if (field.type === "signed-date") {
2905
- return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, className: "field-overlay-value", style: getCssTextStyle(field), children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
2976
+ return /* @__PURE__ */ jsx6(FitText, { maxPt: field.fontSize, shrink: !!field.autoShrink, className: "field-overlay-value", style: textStyle, children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
2906
2977
  }
2907
- const fontStyle = {
2908
- letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
2909
- lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
2910
- ...getCssTextStyle(field)
2911
- };
2912
2978
  if (field.type === "dropdown" || field.options && field.options.length > 0) {
2913
2979
  return /* @__PURE__ */ jsxs6(
2914
2980
  "select",
@@ -2918,7 +2984,7 @@ ${row.join(",")}`, "csv");
2918
2984
  onChange: (e) => handleFieldUpdate(field.id, e.target.value),
2919
2985
  onFocus: () => setSelectedFieldId(field.id),
2920
2986
  onClick: (e) => e.stopPropagation(),
2921
- style: { fontSize: `${field.fontSize}pt`, ...fontStyle },
2987
+ style: { fontSize: `${field.fontSize}pt`, ...textStyle },
2922
2988
  children: [
2923
2989
  /* @__PURE__ */ jsx6("option", { value: "", children: field.placeholder || "Select..." }),
2924
2990
  (field.options || []).map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
@@ -2930,6 +2996,7 @@ ${row.join(",")}`, "csv");
2930
2996
  FitInput,
2931
2997
  {
2932
2998
  maxPt: field.fontSize,
2999
+ shrink: !!field.autoShrink,
2933
3000
  type: field.textSubtype === "email" ? "email" : field.textSubtype === "number" ? "number" : field.textSubtype === "phone" ? "tel" : field.textSubtype === "date" ? "date" : "text",
2934
3001
  className: "field-inline-input",
2935
3002
  value: field.value,
@@ -2938,7 +3005,7 @@ ${row.join(",")}`, "csv");
2938
3005
  onChange: (e) => handleFieldUpdate(field.id, e.target.value),
2939
3006
  onFocus: () => setSelectedFieldId(field.id),
2940
3007
  onClick: (e) => e.stopPropagation(),
2941
- style: fontStyle
3008
+ style: textStyle
2942
3009
  }
2943
3010
  );
2944
3011
  }, [signer, handleFieldUpdate, setSelectedFieldId]);