@unlev/exeq 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -59,6 +59,7 @@ __export(lib_exports, {
59
59
  isRedactField: () => isRedactField,
60
60
  isSignatureField: () => isSignatureField,
61
61
  isTextLikeField: () => isTextLikeField,
62
+ parseDate: () => parseDate,
62
63
  postPdfToCallback: () => postPdfToCallback,
63
64
  preserveFieldValues: () => preserveFieldValues,
64
65
  renderPdfPages: () => renderPdfPages,
@@ -127,6 +128,17 @@ var CSS_FONT_MAP = {
127
128
  function getCssFontFamily(fontFamily) {
128
129
  return fontFamily ? CSS_FONT_MAP[fontFamily] : void 0;
129
130
  }
131
+ function getCssTextStyle(field) {
132
+ const decorations = [];
133
+ if (field.underline) decorations.push("underline");
134
+ if (field.strikethrough) decorations.push("line-through");
135
+ return {
136
+ fontFamily: getCssFontFamily(field.fontFamily),
137
+ fontWeight: field.bold ? "bold" : void 0,
138
+ fontStyle: field.italic ? "italic" : void 0,
139
+ textDecorationLine: decorations.length ? decorations.join(" ") : void 0
140
+ };
141
+ }
130
142
  function sortFieldsByPosition(fields) {
131
143
  return [...fields].sort((a, b) => {
132
144
  if (a.page !== b.page) return a.page - b.page;
@@ -782,6 +794,15 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillCon
782
794
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
783
795
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
784
796
  ] }),
797
+ isText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Formatting" }),
799
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-format-buttons", children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${field.bold ? "active" : ""}`, style: { fontWeight: "bold" }, title: "Bold", onClick: () => onUpdate(field.id, { bold: !field.bold }), children: "B" }),
801
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${field.italic ? "active" : ""}`, style: { fontStyle: "italic" }, title: "Italic", onClick: () => onUpdate(field.id, { italic: !field.italic }), children: "I" }),
802
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${field.underline ? "active" : ""}`, style: { textDecorationLine: "underline" }, title: "Underline", onClick: () => onUpdate(field.id, { underline: !field.underline }), children: "U" }),
803
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
804
+ ] })
805
+ ] }),
785
806
  isText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
786
807
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
787
808
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
@@ -854,6 +875,10 @@ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete })
854
875
  const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
855
876
  const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
856
877
  const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
878
+ const allBold = fields.every((f) => f.bold);
879
+ const allItalic = fields.every((f) => f.italic);
880
+ const allUnderline = fields.every((f) => f.underline);
881
+ const allStrike = fields.every((f) => f.strikethrough);
857
882
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
858
883
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
859
884
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
@@ -968,6 +993,15 @@ function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete })
968
993
  }
969
994
  )
970
995
  ] }),
996
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
997
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Formatting" }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-format-buttons", children: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${allBold ? "active" : ""}`, style: { fontWeight: "bold" }, title: "Bold", onClick: () => onUpdate({ bold: !allBold }), children: "B" }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${allItalic ? "active" : ""}`, style: { fontStyle: "italic" }, title: "Italic", onClick: () => onUpdate({ italic: !allItalic }), children: "I" }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${allUnderline ? "active" : ""}`, style: { textDecorationLine: "underline" }, title: "Underline", onClick: () => onUpdate({ underline: !allUnderline }), children: "U" }),
1002
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: `text-format-btn ${allStrike ? "active" : ""}`, style: { textDecorationLine: "line-through" }, title: "Strikethrough", onClick: () => onUpdate({ strikethrough: !allStrike }), children: "S" })
1003
+ ] })
1004
+ ] }),
971
1005
  hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
972
1006
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
973
1007
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
@@ -1750,7 +1784,6 @@ function DesignerView({
1750
1784
  }
1751
1785
  }
1752
1786
  const inkColor = field.inkColor || "#000000";
1753
- const cssFontFamily = getCssFontFamily(field.fontFamily);
1754
1787
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1755
1788
  "div",
1756
1789
  {
@@ -1758,9 +1791,9 @@ function DesignerView({
1758
1791
  style: {
1759
1792
  color: field.value ? inkColor : void 0,
1760
1793
  fontSize: `${field.fontSize}pt`,
1761
- fontFamily: cssFontFamily,
1762
1794
  letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
1763
- lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0
1795
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
1796
+ ...getCssTextStyle(field)
1764
1797
  },
1765
1798
  children: field.value || field.placeholder
1766
1799
  }
@@ -2128,39 +2161,56 @@ var import_react7 = require("react");
2128
2161
  var import_pdf_lib = require("pdf-lib");
2129
2162
 
2130
2163
  // src/utils/formulaResolver.ts
2164
+ function parseDate(value) {
2165
+ const s = value.trim();
2166
+ if (!s) return null;
2167
+ if (/^\d+(\.\d+)?$/.test(s)) {
2168
+ const serial = parseFloat(s);
2169
+ if (serial > 59) {
2170
+ const utc = new Date(Math.round((serial - 25569) * 864e5));
2171
+ if (!isNaN(utc.getTime())) {
2172
+ return new Date(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
2173
+ }
2174
+ }
2175
+ }
2176
+ const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
2177
+ if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
2178
+ const d = new Date(s);
2179
+ return isNaN(d.getTime()) ? null : d;
2180
+ }
2131
2181
  var BUILTIN_TRANSFORMS = {
2132
2182
  // Date transforms (expects a parseable date string)
2133
2183
  month: (v) => {
2134
- const d = new Date(v);
2135
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
2184
+ const d = parseDate(v);
2185
+ return d ? String(d.getMonth() + 1) : "";
2136
2186
  },
2137
2187
  month2: (v) => {
2138
- const d = new Date(v);
2139
- return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
2188
+ const d = parseDate(v);
2189
+ return d ? String(d.getMonth() + 1).padStart(2, "0") : "";
2140
2190
  },
2141
2191
  monthname: (v) => {
2142
- const d = new Date(v);
2143
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
2192
+ const d = parseDate(v);
2193
+ return d ? d.toLocaleString("en", { month: "long" }) : "";
2144
2194
  },
2145
2195
  monthshort: (v) => {
2146
- const d = new Date(v);
2147
- return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
2196
+ const d = parseDate(v);
2197
+ return d ? d.toLocaleString("en", { month: "short" }) : "";
2148
2198
  },
2149
2199
  day: (v) => {
2150
- const d = new Date(v);
2151
- return isNaN(d.getTime()) ? "" : String(d.getDate());
2200
+ const d = parseDate(v);
2201
+ return d ? String(d.getDate()) : "";
2152
2202
  },
2153
2203
  day2: (v) => {
2154
- const d = new Date(v);
2155
- return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
2204
+ const d = parseDate(v);
2205
+ return d ? String(d.getDate()).padStart(2, "0") : "";
2156
2206
  },
2157
2207
  year: (v) => {
2158
- const d = new Date(v);
2159
- return isNaN(d.getTime()) ? "" : String(d.getFullYear());
2208
+ const d = parseDate(v);
2209
+ return d ? String(d.getFullYear()) : "";
2160
2210
  },
2161
2211
  year2: (v) => {
2162
- const d = new Date(v);
2163
- return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
2212
+ const d = parseDate(v);
2213
+ return d ? String(d.getFullYear()).slice(-2) : "";
2164
2214
  },
2165
2215
  // String transforms
2166
2216
  upper: (v) => v.toUpperCase(),
@@ -2189,15 +2239,27 @@ var BUILTIN_TRANSFORMS = {
2189
2239
  };
2190
2240
  var FORMULA_RE = /\{\{(.+?)\}\}/g;
2191
2241
  var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
2192
- function resolveFormula(formula, fields, customTransforms) {
2242
+ function todayIso() {
2243
+ const d = /* @__PURE__ */ new Date();
2244
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2245
+ }
2246
+ function resolveFormula(formula, fields, customTransforms, values) {
2193
2247
  const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
2194
2248
  return formula.replace(FORMULA_RE, (_match, expr) => {
2195
2249
  const pipeMatch = expr.match(PIPE_RE);
2196
2250
  const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
2197
2251
  const transformName = pipeMatch ? pipeMatch[2].trim() : null;
2198
- const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
2199
- if (!sourceField) return "";
2200
- const rawValue = sourceField.value || "";
2252
+ const lowerLabel = sourceLabel.toLowerCase();
2253
+ const sourceField = fields.find((f) => f.label.toLowerCase() === lowerLabel) || fields.find((f) => f.id === sourceLabel);
2254
+ let rawValue = sourceField ? sourceField.value || "" : void 0;
2255
+ if (rawValue === void 0 && values) {
2256
+ const key = Object.keys(values).find((k) => k.toLowerCase() === lowerLabel);
2257
+ if (key !== void 0) rawValue = values[key] || "";
2258
+ }
2259
+ if (rawValue === void 0 && (lowerLabel === "today" || lowerLabel === "now")) {
2260
+ rawValue = todayIso();
2261
+ }
2262
+ if (rawValue === void 0) return "";
2201
2263
  if (!transformName) return rawValue;
2202
2264
  const fn = transforms[transformName];
2203
2265
  if (!fn) return rawValue;
@@ -2208,20 +2270,28 @@ function resolveFormula(formula, fields, customTransforms) {
2208
2270
  }
2209
2271
  });
2210
2272
  }
2211
- function resolveAllFormulas(fields, customTransforms) {
2273
+ function resolveAllFormulas(fields, customTransforms, values) {
2212
2274
  return fields.map((f) => {
2213
2275
  if (!f.formula) return f;
2214
- const computed = resolveFormula(f.formula, fields, customTransforms);
2276
+ const computed = resolveFormula(f.formula, fields, customTransforms, values);
2215
2277
  return computed !== f.value ? { ...f, value: computed } : f;
2216
2278
  });
2217
2279
  }
2218
2280
 
2219
2281
  // src/utils/pdfFiller.ts
2220
- var FONT_MAP = {
2221
- Helvetica: import_pdf_lib.StandardFonts.Helvetica,
2222
- Courier: import_pdf_lib.StandardFonts.Courier,
2223
- TimesRoman: import_pdf_lib.StandardFonts.TimesRoman
2282
+ var FONT_VARIANTS = {
2283
+ Helvetica: [import_pdf_lib.StandardFonts.Helvetica, import_pdf_lib.StandardFonts.HelveticaBold, import_pdf_lib.StandardFonts.HelveticaOblique, import_pdf_lib.StandardFonts.HelveticaBoldOblique],
2284
+ Courier: [import_pdf_lib.StandardFonts.Courier, import_pdf_lib.StandardFonts.CourierBold, import_pdf_lib.StandardFonts.CourierOblique, import_pdf_lib.StandardFonts.CourierBoldOblique],
2285
+ TimesRoman: [import_pdf_lib.StandardFonts.TimesRoman, import_pdf_lib.StandardFonts.TimesRomanBold, import_pdf_lib.StandardFonts.TimesRomanItalic, import_pdf_lib.StandardFonts.TimesRomanBoldItalic]
2224
2286
  };
2287
+ function resolveStandardFont(family, bold, italic) {
2288
+ const variants = FONT_VARIANTS[family] || FONT_VARIANTS.Helvetica;
2289
+ const idx = (bold ? 1 : 0) + (italic ? 2 : 0);
2290
+ return variants[idx];
2291
+ }
2292
+ function fontKey(family, bold, italic) {
2293
+ return `${family}|${bold ? "b" : ""}|${italic ? "i" : ""}`;
2294
+ }
2225
2295
  var US_LETTER = [612, 792];
2226
2296
  var US_LEGAL = [612, 1008];
2227
2297
  var A4 = [595.28, 841.89];
@@ -2296,7 +2366,7 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2296
2366
  page.drawImage(img, { x, y, width: w, height: h });
2297
2367
  }
2298
2368
  } else {
2299
- const font = await getFont(field.fontFamily || "Helvetica");
2369
+ const font = await getFont(field.fontFamily || "Helvetica", field.bold, field.italic);
2300
2370
  const spacing = field.letterSpacing || 0;
2301
2371
  const textWidthAtSize = (text, size) => font.widthOfTextAtSize(text, size) + spacing * (text.length - 1);
2302
2372
  let fontSize = field.autoShrink ? Math.min(field.fontSize, h * 0.7) : field.fontSize;
@@ -2307,13 +2377,14 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2307
2377
  fontSize -= 0.5;
2308
2378
  }
2309
2379
  }
2380
+ const textX = x + 2;
2381
+ const baselineY = y + h * 0.3;
2310
2382
  if (spacing > 0) {
2311
- let cx = x + 2;
2312
- const cy = y + h * 0.3;
2383
+ let cx = textX;
2313
2384
  for (const char of field.value) {
2314
2385
  page.drawText(char, {
2315
2386
  x: cx,
2316
- y: cy,
2387
+ y: baselineY,
2317
2388
  size: fontSize,
2318
2389
  font,
2319
2390
  color: inkColor
@@ -2322,13 +2393,35 @@ async function renderFieldsOnPages(pages, fields, getFont, getSignature) {
2322
2393
  }
2323
2394
  } else {
2324
2395
  page.drawText(field.value, {
2325
- x: x + 2,
2326
- y: y + h * 0.3,
2396
+ x: textX,
2397
+ y: baselineY,
2327
2398
  size: fontSize,
2328
2399
  font,
2329
2400
  color: inkColor
2330
2401
  });
2331
2402
  }
2403
+ if (field.underline || field.strikethrough) {
2404
+ const textWidth = textWidthAtSize(field.value, fontSize);
2405
+ const lineThickness = Math.max(0.5, fontSize * 0.06);
2406
+ if (field.underline) {
2407
+ const uy = baselineY - fontSize * 0.12;
2408
+ page.drawLine({
2409
+ start: { x: textX, y: uy },
2410
+ end: { x: textX + textWidth, y: uy },
2411
+ thickness: lineThickness,
2412
+ color: inkColor
2413
+ });
2414
+ }
2415
+ if (field.strikethrough) {
2416
+ const sy = baselineY + fontSize * 0.3;
2417
+ page.drawLine({
2418
+ start: { x: textX, y: sy },
2419
+ end: { x: textX + textWidth, y: sy },
2420
+ thickness: lineThickness,
2421
+ color: inkColor
2422
+ });
2423
+ }
2424
+ }
2332
2425
  }
2333
2426
  }
2334
2427
  }
@@ -2342,18 +2435,19 @@ async function createPdfBuilder(defaults = {}) {
2342
2435
  const fontCache = /* @__PURE__ */ new Map();
2343
2436
  const embeddedPagesByUrl = /* @__PURE__ */ new Map();
2344
2437
  const signatureCache = /* @__PURE__ */ new Map();
2345
- async function getFont(family) {
2346
- let f = fontCache.get(family);
2438
+ async function getFont(family, bold, italic) {
2439
+ const key = fontKey(family, bold, italic);
2440
+ let f = fontCache.get(key);
2347
2441
  if (!f) {
2348
- const stdName = FONT_MAP[family] || import_pdf_lib.StandardFonts.Helvetica;
2349
- f = await doc.embedFont(stdName);
2350
- fontCache.set(family, f);
2442
+ f = await doc.embedFont(resolveStandardFont(family, bold, italic));
2443
+ fontCache.set(key, f);
2351
2444
  }
2352
2445
  return f;
2353
2446
  }
2354
2447
  async function ensureFonts(fields) {
2355
- const keys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
2356
- for (const k of keys) await getFont(k);
2448
+ const keys = /* @__PURE__ */ new Map();
2449
+ for (const f of fields) keys.set(fontKey(f.fontFamily || "Helvetica", f.bold, f.italic), f);
2450
+ for (const f of keys.values()) await getFont(f.fontFamily || "Helvetica", f.bold, f.italic);
2357
2451
  }
2358
2452
  async function embedSource(source) {
2359
2453
  if (typeof source === "string") {
@@ -2386,7 +2480,7 @@ async function createPdfBuilder(defaults = {}) {
2386
2480
  doc,
2387
2481
  async addRecord(opts) {
2388
2482
  const pageSize = opts.pageSize ?? defaults.pageSize;
2389
- let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms) : opts.fields;
2483
+ let fields = opts.resolveFormulas ? resolveAllFormulas(opts.fields, opts.customTransforms, opts.formulaValues) : opts.fields;
2390
2484
  if (opts.calibration) {
2391
2485
  fields = applyCalibration(
2392
2486
  fields,
@@ -2830,7 +2924,7 @@ ${row.join(",")}`, "csv");
2830
2924
  fontSize: `${field.fontSize}pt`,
2831
2925
  letterSpacing: field.letterSpacing ? `${field.letterSpacing}pt` : void 0,
2832
2926
  lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
2833
- fontFamily: getCssFontFamily(field.fontFamily)
2927
+ ...getCssTextStyle(field)
2834
2928
  };
2835
2929
  if (field.type === "dropdown" || field.options && field.options.length > 0) {
2836
2930
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -3109,6 +3203,7 @@ function SignerRoleSelector({
3109
3203
  isRedactField,
3110
3204
  isSignatureField,
3111
3205
  isTextLikeField,
3206
+ parseDate,
3112
3207
  postPdfToCallback,
3113
3208
  preserveFieldValues,
3114
3209
  renderPdfPages,