@freereportjs/core 0.9.0 → 0.10.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
@@ -186,138 +186,6 @@ function pdfFonts(pages) {
186
186
  return out;
187
187
  }
188
188
 
189
- // src/version.ts
190
- var version = "0.9.0";
191
- var releaseDate = "2026-10-16";
192
-
193
- // src/license.ts
194
- var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "MiJXJR8TZJ_-0VKk0wIQ-5mzRsyQ6ruGsbcRRdw9jrw", y: "MTdklYSzCM2pganffz7PPMfxSclQkBLqU_9se_VDznM" };
195
- var PRICING_URL = "https://freereport.app/pricing/";
196
- var USAGE_URL = "https://freereport.app/api/usage";
197
- var STATUS_URL = "https://freereport.app/api/domain-status";
198
- var b64urlDecode = (s) => {
199
- const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
200
- return Uint8Array.from(bin, (c) => c.charCodeAt(0));
201
- };
202
- function isDevHost(host) {
203
- const h = host.toLowerCase().replace(/^\[|\]$/g, "");
204
- return !h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local") || /^(10\.\d+|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d+\.\d+$/.test(h);
205
- }
206
- function domainMatches(host, domains) {
207
- const h = host.toLowerCase().replace(/\.$/, "");
208
- return domains.some((d) => {
209
- const raw = d.toLowerCase().replace(/\.$/, "");
210
- const wildcard = raw.startsWith("*.");
211
- const dom = wildcard ? raw.slice(2) : raw;
212
- if (!dom) return false;
213
- if (h === dom || h === `www.${dom}`) return true;
214
- return wildcard && h.endsWith(`.${dom}`);
215
- });
216
- }
217
- var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
218
- async function verifyLicense(key2, host, opts = {}) {
219
- const parts = typeof key2 === "string" ? key2.trim().split(".") : [];
220
- if (parts.length !== 3 || parts[0] !== "FR1") return { ok: false, reason: "format" };
221
- let info;
222
- try {
223
- info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
224
- if (!Array.isArray(info.domains) || typeof info.expires !== "string") return { ok: false, reason: "format" };
225
- } catch {
226
- return { ok: false, reason: "format" };
227
- }
228
- const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
229
- if (!subtle) return { ok: false, reason: "unsupported", info };
230
- try {
231
- const pub = await subtle.importKey("jwk", opts.publicKey ?? PUBLIC_KEY, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
232
- const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
233
- const ok = await subtle.verify({ name: "ECDSA", hash: "SHA-256" }, pub, b64urlDecode(parts[2]), signed);
234
- if (!ok) return { ok: false, reason: "signature", info };
235
- } catch {
236
- return { ok: false, reason: "signature", info };
237
- }
238
- if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
239
- if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
240
- if (info.updates && (opts.released ?? releaseDate) > info.updates) return { ok: false, reason: "updates", info };
241
- return { ok: true, info };
242
- }
243
- var globalKey;
244
- var statuses = /* @__PURE__ */ new Map();
245
- var badgeCheck = null;
246
- function setLicenseKey(key2) {
247
- globalKey = key2;
248
- }
249
- var NOTICE = {
250
- missing: `FreeReport is free for personal use. Company use needs a domain license (\u20A9500,000 per domain per year, or \u20A9900,000 perpetual): ${PRICING_URL}
251
- FreeReport\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\uC0AC\uC5D0\uC11C \uC4F0\uB824\uBA74 \uB3C4\uBA54\uC778 \uB77C\uC774\uC120\uC2A4(\uB3C4\uBA54\uC778\uB2F9 \uC5F0 50\uB9CC \uC6D0, \uC601\uAD6C 90\uB9CC \uC6D0)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
252
- format: "FreeReport: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
253
- signature: "FreeReport: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
254
- expired: "FreeReport: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
255
- domain: "FreeReport: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4.",
256
- updates: "FreeReport: this version was released after the update period of your perpetual license. / \uC601\uAD6C \uB77C\uC774\uC120\uC2A4\uC758 \uC5C5\uB370\uC774\uD2B8 \uAE30\uAC04 \uB4A4\uC5D0 \uB098\uC628 \uBC84\uC804\uC785\uB2C8\uB2E4."
257
- };
258
- function reportUsage(info, host = typeof location !== "undefined" ? location.hostname : "") {
259
- try {
260
- if (isDevHost(host) || typeof navigator === "undefined") return;
261
- const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
262
- const mark = `${host}|${day}|${info.licensed ? 1 : 0}`;
263
- const store = typeof localStorage !== "undefined" ? localStorage : null;
264
- if (store?.getItem("freereport:usage") === mark) return;
265
- store?.setItem("freereport:usage", mark);
266
- const body = JSON.stringify({ version: info.version, licensed: info.licensed, company: info.company, url: `${location.origin}${location.pathname}` });
267
- if (typeof navigator.sendBeacon === "function") navigator.sendBeacon(USAGE_URL, body);
268
- else if (typeof fetch === "function") void fetch(USAGE_URL, { method: "POST", body, mode: "no-cors", keepalive: true }).catch(() => void 0);
269
- } catch {
270
- }
271
- }
272
- function checkLicense(key2 = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
273
- if (isDevHost(host)) return Promise.resolve(true);
274
- const k = key2 ?? globalKey;
275
- const id = `${k ?? ""}@${host}`;
276
- let status = statuses.get(id);
277
- if (status) return status;
278
- if (!k) {
279
- console.info(NOTICE.missing);
280
- reportUsage({ version, licensed: false }, host);
281
- status = Promise.resolve(false);
282
- } else {
283
- status = verifyLicense(k, host).then((res) => {
284
- reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
285
- if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
286
- const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
287
- console.warn(`${NOTICE[res.reason]}${extra}
288
- ${PRICING_URL}`);
289
- return false;
290
- }, () => false);
291
- }
292
- statuses.set(id, status);
293
- return status;
294
- }
295
- function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
296
- if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
297
- if (badgeCheck) return badgeCheck;
298
- const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
299
- let store = null;
300
- try {
301
- store = typeof localStorage !== "undefined" ? localStorage : null;
302
- const cached = JSON.parse(store?.getItem("freereport:badge") ?? "null");
303
- if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
304
- } catch {
305
- }
306
- badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
307
- const show = d.badge === true;
308
- try {
309
- store?.setItem("freereport:badge", JSON.stringify({ host, day, show }));
310
- } catch {
311
- }
312
- return show;
313
- }, () => false);
314
- return badgeCheck;
315
- }
316
- async function needsFreeBadge(key2, host = typeof location !== "undefined" ? location.hostname : "") {
317
- if (await checkLicense(key2, host)) return false;
318
- return badgeRequested(host);
319
- }
320
-
321
189
  // src/format.ts
322
190
  var DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
323
191
  var SMALL = ["", "\uC2ED", "\uBC31", "\uCC9C"];
@@ -499,6 +367,138 @@ function formatValue(v, format, locale = "ko") {
499
367
  return String(v);
500
368
  }
501
369
 
370
+ // src/version.ts
371
+ var version = "0.10.1";
372
+ var releaseDate = "2026-10-19";
373
+
374
+ // src/license.ts
375
+ var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "MiJXJR8TZJ_-0VKk0wIQ-5mzRsyQ6ruGsbcRRdw9jrw", y: "MTdklYSzCM2pganffz7PPMfxSclQkBLqU_9se_VDznM" };
376
+ var PRICING_URL = "https://freereport.app/pricing/";
377
+ var USAGE_URL = "https://freereport.app/api/usage";
378
+ var STATUS_URL = "https://freereport.app/api/domain-status";
379
+ var b64urlDecode = (s) => {
380
+ const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
381
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0));
382
+ };
383
+ function isDevHost(host) {
384
+ const h = host.toLowerCase().replace(/^\[|\]$/g, "");
385
+ return !h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local") || /^(10\.\d+|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d+\.\d+$/.test(h);
386
+ }
387
+ function domainMatches(host, domains) {
388
+ const h = host.toLowerCase().replace(/\.$/, "");
389
+ return domains.some((d) => {
390
+ const raw = d.toLowerCase().replace(/\.$/, "");
391
+ const wildcard = raw.startsWith("*.");
392
+ const dom = wildcard ? raw.slice(2) : raw;
393
+ if (!dom) return false;
394
+ if (h === dom || h === `www.${dom}`) return true;
395
+ return wildcard && h.endsWith(`.${dom}`);
396
+ });
397
+ }
398
+ var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
399
+ async function verifyLicense(key2, host, opts = {}) {
400
+ const parts = typeof key2 === "string" ? key2.trim().split(".") : [];
401
+ if (parts.length !== 3 || parts[0] !== "FR1") return { ok: false, reason: "format" };
402
+ let info;
403
+ try {
404
+ info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
405
+ if (!Array.isArray(info.domains) || typeof info.expires !== "string") return { ok: false, reason: "format" };
406
+ } catch {
407
+ return { ok: false, reason: "format" };
408
+ }
409
+ const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
410
+ if (!subtle) return { ok: false, reason: "unsupported", info };
411
+ try {
412
+ const pub = await subtle.importKey("jwk", opts.publicKey ?? PUBLIC_KEY, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
413
+ const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
414
+ const ok = await subtle.verify({ name: "ECDSA", hash: "SHA-256" }, pub, b64urlDecode(parts[2]), signed);
415
+ if (!ok) return { ok: false, reason: "signature", info };
416
+ } catch {
417
+ return { ok: false, reason: "signature", info };
418
+ }
419
+ if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
420
+ if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
421
+ if (info.updates && (opts.released ?? releaseDate) > info.updates) return { ok: false, reason: "updates", info };
422
+ return { ok: true, info };
423
+ }
424
+ var globalKey;
425
+ var statuses = /* @__PURE__ */ new Map();
426
+ var badgeCheck = null;
427
+ function setLicenseKey(key2) {
428
+ globalKey = key2;
429
+ }
430
+ var NOTICE = {
431
+ missing: `FreeReport is free for personal use. Company use needs a domain license (\u20A9500,000 per domain per year, or \u20A9900,000 perpetual): ${PRICING_URL}
432
+ FreeReport\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\uC0AC\uC5D0\uC11C \uC4F0\uB824\uBA74 \uB3C4\uBA54\uC778 \uB77C\uC774\uC120\uC2A4(\uB3C4\uBA54\uC778\uB2F9 \uC5F0 50\uB9CC \uC6D0, \uC601\uAD6C 90\uB9CC \uC6D0)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
433
+ format: "FreeReport: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
434
+ signature: "FreeReport: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
435
+ expired: "FreeReport: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
436
+ domain: "FreeReport: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4.",
437
+ updates: "FreeReport: this version was released after the update period of your perpetual license. / \uC601\uAD6C \uB77C\uC774\uC120\uC2A4\uC758 \uC5C5\uB370\uC774\uD2B8 \uAE30\uAC04 \uB4A4\uC5D0 \uB098\uC628 \uBC84\uC804\uC785\uB2C8\uB2E4."
438
+ };
439
+ function reportUsage(info, host = typeof location !== "undefined" ? location.hostname : "") {
440
+ try {
441
+ if (isDevHost(host) || typeof navigator === "undefined") return;
442
+ const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
443
+ const mark = `${host}|${day}|${info.licensed ? 1 : 0}`;
444
+ const store = typeof localStorage !== "undefined" ? localStorage : null;
445
+ if (store?.getItem("freereport:usage") === mark) return;
446
+ store?.setItem("freereport:usage", mark);
447
+ const body = JSON.stringify({ version: info.version, licensed: info.licensed, company: info.company, url: `${location.origin}${location.pathname}` });
448
+ if (typeof navigator.sendBeacon === "function") navigator.sendBeacon(USAGE_URL, body);
449
+ else if (typeof fetch === "function") void fetch(USAGE_URL, { method: "POST", body, mode: "no-cors", keepalive: true }).catch(() => void 0);
450
+ } catch {
451
+ }
452
+ }
453
+ function checkLicense(key2 = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
454
+ if (isDevHost(host)) return Promise.resolve(true);
455
+ const k = key2 ?? globalKey;
456
+ const id = `${k ?? ""}@${host}`;
457
+ let status = statuses.get(id);
458
+ if (status) return status;
459
+ if (!k) {
460
+ console.info(NOTICE.missing);
461
+ reportUsage({ version, licensed: false }, host);
462
+ status = Promise.resolve(false);
463
+ } else {
464
+ status = verifyLicense(k, host).then((res) => {
465
+ reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
466
+ if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
467
+ const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
468
+ console.warn(`${NOTICE[res.reason]}${extra}
469
+ ${PRICING_URL}`);
470
+ return false;
471
+ }, () => false);
472
+ }
473
+ statuses.set(id, status);
474
+ return status;
475
+ }
476
+ function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
477
+ if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
478
+ if (badgeCheck) return badgeCheck;
479
+ const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
480
+ let store = null;
481
+ try {
482
+ store = typeof localStorage !== "undefined" ? localStorage : null;
483
+ const cached = JSON.parse(store?.getItem("freereport:badge") ?? "null");
484
+ if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
485
+ } catch {
486
+ }
487
+ badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
488
+ const show = d.badge === true;
489
+ try {
490
+ store?.setItem("freereport:badge", JSON.stringify({ host, day, show }));
491
+ } catch {
492
+ }
493
+ return show;
494
+ }, () => false);
495
+ return badgeCheck;
496
+ }
497
+ async function needsFreeBadge(key2, host = typeof location !== "undefined" ? location.hostname : "") {
498
+ if (await checkLicense(key2, host)) return false;
499
+ return badgeRequested(host);
500
+ }
501
+
502
502
  // src/expr.ts
503
503
  var OPS = ["===", "!==", "==", "!=", "<=", ">=", "&&", "||", "??", "<", ">", "+", "-", "*", "/", "%", "!", "?", ":", "(", ")", ",", ".", "[", "]"];
504
504
  function tokenize(src) {
@@ -933,7 +933,7 @@ function checkExpression(src) {
933
933
  return e.message;
934
934
  }
935
935
  }
936
- function interpolate(template, scope) {
936
+ function interpolate(template, scope, locale) {
937
937
  if (!template.includes("{")) return template.replace(/}}/g, "}");
938
938
  let out = "";
939
939
  let i = 0;
@@ -961,7 +961,7 @@ function interpolate(template, scope) {
961
961
  }
962
962
  const { expr, format } = splitFormat(template.slice(i + 1, end));
963
963
  const v = evaluate(expr, scope);
964
- out += formatValue(v, format);
964
+ out += formatValue(v, format, locale);
965
965
  i = end + 1;
966
966
  }
967
967
  return out;
@@ -1038,8 +1038,8 @@ function textMark(text, cx, cy, size, opts) {
1038
1038
  source: { band: "watermark" }
1039
1039
  };
1040
1040
  }
1041
- function watermarkItems(wm, page, scope, measure) {
1042
- const text = wm.text ? interpolate(wm.text, scope) : "";
1041
+ function watermarkItems(wm, page, scope, measure, locale) {
1042
+ const text = wm.text ? interpolate(wm.text, scope, locale) : "";
1043
1043
  const opacity = wm.opacity ?? 0.12;
1044
1044
  const angle = wm.angle ?? -35;
1045
1045
  const color = wm.color ?? "#808080";
@@ -1191,7 +1191,7 @@ var shift = (d, dx, dy) => {
1191
1191
  }
1192
1192
  return out;
1193
1193
  };
1194
- function embossItems(em, page, scope, measure) {
1194
+ function embossItems(em, page, scope, measure, locale) {
1195
1195
  const w = em.w ?? 32, h = em.h ?? (em.shape === "rect" ? 20 : w);
1196
1196
  const x = em.x ?? page.width - page.margins[1] - w - 4;
1197
1197
  const y = em.y ?? page.height - page.margins[2] - h - 12;
@@ -1210,8 +1210,8 @@ function embossItems(em, page, scope, measure) {
1210
1210
  } else if (shape === "rect") {
1211
1211
  shapes.push(rectRing(x, y, w, h, Math.max(0.6, Math.min(w, h) * 0.05)));
1212
1212
  }
1213
- const text = em.text ? interpolate(em.text, scope) : "";
1214
- const center = em.centerText ? interpolate(em.centerText, scope) : "";
1213
+ const text = em.text ? interpolate(em.text, scope, locale) : "";
1214
+ const center = em.centerText ? interpolate(em.centerText, scope, locale) : "";
1215
1215
  if (shape === "circle" && text) {
1216
1216
  const r = Math.min(w, h) / 2;
1217
1217
  const size = em.fontSize ?? Math.max(5, r * 0.62);
@@ -1250,11 +1250,11 @@ function decoratePages(pages, marks, geometry, scopeFor, measure) {
1250
1250
  const back = [], front = [];
1251
1251
  for (const wm of wms) {
1252
1252
  if (!onPage(wm.pages, i, pages.length) || !wm.text && !wm.image) continue;
1253
- (wm.layer === "front" ? front : back).push(...watermarkItems(wm, page, scope, measure));
1253
+ (wm.layer === "front" ? front : back).push(...watermarkItems(wm, page, scope, measure, marks.locale));
1254
1254
  }
1255
1255
  for (const em of ems) {
1256
1256
  if (!onPage(em.pages, i, pages.length)) continue;
1257
- front.push(...embossItems(em, { ...page, margins: geometry.margins }, scope, measure));
1257
+ front.push(...embossItems(em, { ...page, margins: geometry.margins }, scope, measure, marks.locale));
1258
1258
  }
1259
1259
  page.items = [...back, ...page.items, ...front];
1260
1260
  });
@@ -1335,6 +1335,11 @@ function pathData(d) {
1335
1335
  return d.map((v) => typeof v === "number" ? n(v) : v).join(" ");
1336
1336
  }
1337
1337
  var opacityAttr = (o) => o !== void 0 && o < 1 ? ` opacity="${n(o)}"` : "";
1338
+ var sourceAttrs = (it) => {
1339
+ const s = it.source;
1340
+ if (!s?.elementId) return "";
1341
+ return ` data-fr-id="${esc(s.elementId)}" data-fr-kind="${it.kind}"${s.row !== void 0 ? ` data-fr-row="${s.row}"` : ""}`;
1342
+ };
1338
1343
  function textSvg(t, clipId) {
1339
1344
  const sizeMm = t.font.size * PT;
1340
1345
  const family = `${quoteFamily(t.font.family)}, ${FALLBACK_FONTS}`.replace(/"/g, "'");
@@ -1348,11 +1353,12 @@ function textSvg(t, clipId) {
1348
1353
  }
1349
1354
  const overflow = t.source?.band !== "watermark" && t.lines.some((l) => l.x < t.x - 0.05 || l.x + l.width > t.x + t.w + 0.05 || l.y - sizeMm * 0.8 < t.y - 0.05 || l.y + sizeMm * 0.2 > t.y + t.h + 0.05);
1350
1355
  const transform = t.rotate ? ` transform="rotate(${n(t.rotate)} ${n(t.x + t.w / 2)} ${n(t.y + t.h / 2)})"` : "";
1351
- if (!overflow) return { defs: "", body: `<g ${attrs}${transform}${opacityAttr(t.opacity)}>${body}</g>` };
1356
+ const hooks = sourceAttrs(t) + (t.overflow && t.source?.band !== "watermark" ? ' data-fr-overflow="1"' : "");
1357
+ if (!overflow) return { defs: "", body: `<g ${attrs}${hooks}${transform}${opacityAttr(t.opacity)}>${body}</g>` };
1352
1358
  const id = clipId();
1353
1359
  return {
1354
1360
  defs: `<clipPath id="${id}"><rect x="${n(t.x)}" y="${n(t.y)}" width="${n(t.w)}" height="${n(t.h)}"/></clipPath>`,
1355
- body: `<g ${attrs} clip-path="url(#${id})"${transform}${opacityAttr(t.opacity)}>${body}</g>`
1361
+ body: `<g ${attrs}${hooks} clip-path="url(#${id})"${transform}${opacityAttr(t.opacity)}>${body}</g>`
1356
1362
  };
1357
1363
  }
1358
1364
  function itemSvg(it, clipId) {
@@ -1401,7 +1407,8 @@ function pageToSvg(page, opts = {}) {
1401
1407
  for (const it of page.items) {
1402
1408
  const r = itemSvg(it, clipId);
1403
1409
  defs += r.defs;
1404
- body += r.body;
1410
+ const hooks = it.kind === "text" ? "" : sourceAttrs(it);
1411
+ body += !hooks || !r.body ? r.body : r.body.indexOf("<", 1) < 0 ? r.body.replace(/^<(\w+)/, (_, tag) => `<${tag}${hooks}`) : `<g${hooks}>${r.body}</g>`;
1405
1412
  }
1406
1413
  const size = opts.scale ? `width="${n(page.width * opts.scale)}" height="${n(page.height * opts.scale)}"` : `width="${n(page.width)}mm" height="${n(page.height)}mm"`;
1407
1414
  return `<svg xmlns="http://www.w3.org/2000/svg" ${size} viewBox="0 0 ${n(page.width)} ${n(page.height)}"${opts.className ? ` class="${opts.className}"` : ""} text-rendering="geometricPrecision">${defs ? `<defs>${defs}</defs>` : ""}<rect width="100%" height="100%" fill="#fff"/>${body}</svg>`;
@@ -2541,7 +2548,10 @@ function renderChart(el, scope, box, ctx) {
2541
2548
  const labelStyle = { ...style, padding: [0, 0, 0, 0], letterSpacing: 0, border: {} };
2542
2549
  const lh = fontSize * PT * 1.3;
2543
2550
  const items = [];
2544
- const label = (text, x2, y2, w2, align, bold = false, color = "#444444") => items.push({ ...textItem(x2, y2, w2, lh, [text], { ...font, bold }, { ...labelStyle, align, valign: "middle", color }, ctx.measure), source: { band: ctx.kind, elementId: el.id } });
2551
+ const label = (text, x2, y2, w2, align, bold = false, color = "#444444") => (
2552
+ // chart labels are allowed to run past their slot: not an overflow of the element's text
2553
+ items.push({ ...textItem(x2, y2, w2, lh, [text], { ...font, bold }, { ...labelStyle, align, valign: "middle", color }, ctx.measure), overflow: void 0, source: { band: ctx.kind, elementId: el.id } })
2554
+ );
2545
2555
  let { x, y, w, h } = box;
2546
2556
  if (el.title) {
2547
2557
  label(el.title, x, y, w, "center", true, style.color);
@@ -2900,7 +2910,7 @@ function prepare(el, scope, ctx, bandCanGrow) {
2900
2910
  let text = "";
2901
2911
  let value;
2902
2912
  if (!ctx.blank) {
2903
- if (el.type === "text") text = interpolate(el.text ?? "", scope);
2913
+ if (el.type === "text") text = interpolate(el.text ?? "", scope, ctx.locale);
2904
2914
  else {
2905
2915
  value = evaluate(el.expr, scope);
2906
2916
  text = value == null || value === "" ? el.nullText ?? "" : formatValue(value, el.format, ctx.locale);
@@ -2945,6 +2955,7 @@ function textItem(x, y, w, h, lines, font, st, measure) {
2945
2955
  else if (st.align === "right") lx = x + w - pr - width;
2946
2956
  return { text, x: lx, y: top + i * lineH + (lineH - font.size * PT) / 2 + font.size * PT * ASCENT, width };
2947
2957
  });
2958
+ const overflow = top < y - 0.05 || top + total > y + h + 0.05 || out.some((l) => l.x < x - 0.05 || l.x + l.width > x + w + 0.05);
2948
2959
  return {
2949
2960
  kind: "text",
2950
2961
  x,
@@ -2959,6 +2970,7 @@ function textItem(x, y, w, h, lines, font, st, measure) {
2959
2970
  underline: st.underline || void 0,
2960
2971
  strike: st.strike || void 0,
2961
2972
  letterSpacing: st.letterSpacing || void 0,
2973
+ overflow: overflow || void 0,
2962
2974
  text: lines.join("\n")
2963
2975
  };
2964
2976
  }
@@ -3027,25 +3039,106 @@ function barcodeItems(el, x, y, w, h, scope, ctx) {
3027
3039
  return [textItem(x, y, w, h, ["(barcode?)"], { family: "sans-serif", size: 7, bold: false, italic: false }, { ...resolveStyle(ctx.def, el), color: "#cc0000" }, ctx.measure)];
3028
3040
  }
3029
3041
  }
3042
+ var topOf = (el) => Math.min(el.y, el.y + el.h);
3043
+ var bottomOf = (el) => Math.max(el.y, el.y + el.h);
3044
+ var leftOf = (el) => Math.min(el.x, el.x + el.w);
3045
+ var rightOf = (el) => Math.max(el.x, el.x + el.w);
3046
+ function shifts(slots) {
3047
+ let changed = false;
3048
+ for (const s of slots) if (s.collapsed || !s.hidden && s.needed > s.el.h + 0.01) {
3049
+ changed = true;
3050
+ break;
3051
+ }
3052
+ if (!changed) return null;
3053
+ const off = /* @__PURE__ */ new Map();
3054
+ const order = [...slots].sort((a, b) => topOf(a.el) - topOf(b.el));
3055
+ const growth = (q) => q.hidden ? 0 : Math.max(0, q.needed - q.el.h);
3056
+ for (const p of order) {
3057
+ if (p.el.shift === false) continue;
3058
+ const pt = topOf(p.el), pl = leftOf(p.el), pr = rightOf(p.el);
3059
+ let nearest3 = null;
3060
+ let floor = -Infinity, pushed = -Infinity;
3061
+ for (const q of order) {
3062
+ if (topOf(q.el) >= pt) break;
3063
+ if (bottomOf(q.el) > pt + 0.01 || leftOf(q.el) >= pr - 0.01 || pl >= rightOf(q.el) - 0.01) continue;
3064
+ if (!nearest3 || bottomOf(q.el) > bottomOf(nearest3.el) + 0.01) nearest3 = q;
3065
+ if (q.collapsed) continue;
3066
+ const by2 = (off.get(q) ?? 0) + growth(q);
3067
+ if (by2 > 0) pushed = Math.max(pushed, by2);
3068
+ floor = Math.max(floor, bottomOf(q.el) + by2 - pt);
3069
+ }
3070
+ if (!nearest3) continue;
3071
+ const base2 = nearest3.collapsed ? topOf(nearest3.el) + (off.get(nearest3) ?? 0) - pt : (off.get(nearest3) ?? 0) + growth(nearest3);
3072
+ const by = Math.max(base2, pushed, floor);
3073
+ if (Math.abs(by) > 1e-9) off.set(p, by);
3074
+ }
3075
+ return off;
3076
+ }
3077
+ function stretchRows(slots) {
3078
+ for (const s of slots) {
3079
+ if (s.el.stretch !== "row" || s.hidden) continue;
3080
+ for (const o of slots) {
3081
+ if (!o.hidden && Math.abs(o.el.y - s.el.y) < 0.01 && Math.abs(o.el.h - s.el.h) < 0.01) s.needed = Math.max(s.needed, o.needed);
3082
+ }
3083
+ }
3084
+ }
3085
+ function addAnchor(out, el, ctx, left, top, y, h, hidden) {
3086
+ if (!el.id) return;
3087
+ const line = el.type === "line";
3088
+ out.push({
3089
+ id: el.id,
3090
+ band: ctx.kind,
3091
+ row: ctx.rowIndex,
3092
+ x: left + (line ? Math.min(el.x, el.x + el.w) : el.x),
3093
+ y: top + (line ? Math.min(y, y + h) : y),
3094
+ w: line ? Math.abs(el.w) : el.w,
3095
+ h: line ? Math.abs(h) : h,
3096
+ ...hidden ? { hidden } : {}
3097
+ });
3098
+ }
3030
3099
  function renderBand(band, scope, ctx, left, top, minHeight = 0) {
3031
3100
  const bandGrow = band.canGrow ?? (ctx.kind === "detail" || ctx.kind === "groupHeader" || ctx.kind === "groupFooter" || ctx.kind === "summary" || ctx.kind === "title");
3032
- const prepared = [];
3101
+ const slots = [];
3102
+ const anchors = ctx.anchors && !ctx.blank ? [] : void 0;
3103
+ let collapsed = false, rows = false;
3033
3104
  for (const el of band.elements) {
3034
- if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope))) continue;
3035
- prepared.push(prepare(el, scope, ctx, bandGrow));
3105
+ if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope))) {
3106
+ if (el.collapse) collapsed = true;
3107
+ slots.push({ el, style: resolveStyle(ctx.def, el), needed: el.h, hidden: true, collapsed: !!el.collapse });
3108
+ continue;
3109
+ }
3110
+ if (el.stretch === "row") rows = true;
3111
+ slots.push(prepare(el, scope, ctx, bandGrow));
3036
3112
  }
3113
+ if (rows) stretchRows(slots);
3114
+ const shift2 = shifts(slots);
3115
+ const yOf = (s) => shift2 ? s.el.y + (shift2.get(s) ?? 0) : s.el.y;
3037
3116
  let height = Math.max(band.height, minHeight);
3038
- for (const p of prepared) height = Math.max(height, p.el.y + p.needed);
3117
+ if (collapsed) {
3118
+ let lowest = 0, lowestNew = 0;
3119
+ for (const s of slots) {
3120
+ lowest = Math.max(lowest, bottomOf(s.el));
3121
+ if (!s.hidden) lowestNew = Math.max(lowestNew, yOf(s) + Math.max(s.needed, 0));
3122
+ }
3123
+ height = Math.max(minHeight, lowestNew + Math.max(0, band.height - lowest));
3124
+ } else for (const s of slots) if (!s.hidden) height = Math.max(height, yOf(s) + s.needed);
3039
3125
  const items = [];
3040
3126
  const bg = ctx.background ?? band.background;
3041
3127
  if (bg) items.push({ kind: "box", x: left, y: top, w: ctx.width, h: height, fill: bg, source: { band: ctx.kind, row: ctx.rowIndex } });
3042
- const growth = height - band.height;
3043
- for (const p of prepared) {
3128
+ for (const p of slots) {
3044
3129
  const el = p.el;
3130
+ const elY = yOf(p);
3131
+ if (p.hidden) {
3132
+ if (anchors) addAnchor(anchors, el, ctx, left, top, elY, el.h, true);
3133
+ continue;
3134
+ }
3045
3135
  const touchesBottom = el.y + el.h >= band.height - 0.01;
3136
+ const follows = el.stretch === "row" ? touchesBottom : el.stretch ?? touchesBottom;
3046
3137
  let h = p.needed;
3047
- if ((el.stretch ?? touchesBottom) && el.type !== "line" && el.type !== "table" && el.type !== "crosstab") h = Math.max(h, el.h + growth, height - el.y);
3048
- const x = left + el.x, y = top + el.y;
3138
+ if (follows && el.type !== "line" && el.type !== "table" && el.type !== "crosstab") h = Math.max(h, height - elY);
3139
+ if (el.type === "line") h = el.h === 0 ? 0 : el.stretch === "row" ? p.needed : touchesBottom ? Math.max(el.h, height - elY) : el.h;
3140
+ const x = left + el.x, y = top + elY;
3141
+ if (anchors) addAnchor(anchors, el, ctx, left, top, elY, h);
3049
3142
  switch (el.type) {
3050
3143
  case "text":
3051
3144
  case "field": {
@@ -3060,8 +3153,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
3060
3153
  break;
3061
3154
  }
3062
3155
  case "line": {
3063
- const lh = el.h === 0 ? 0 : el.y + el.h >= band.height - 0.01 ? el.h + growth : el.h;
3064
- items.push({ kind: "line", x1: x, y1: y, x2: x + el.w, y2: y + lh, width: el.width ?? 0.2, color: el.color ?? "#000000", dash: el.dash, source: { band: ctx.kind, elementId: el.id } });
3156
+ items.push({ kind: "line", x1: x, y1: y, x2: x + el.w, y2: y + h, width: el.width ?? 0.2, color: el.color ?? "#000000", dash: el.dash, source: { band: ctx.kind, elementId: el.id } });
3065
3157
  break;
3066
3158
  }
3067
3159
  case "rect": {
@@ -3098,7 +3190,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
3098
3190
  break;
3099
3191
  }
3100
3192
  }
3101
- return { height, items };
3193
+ return anchors ? { height, items, anchors } : { height, items };
3102
3194
  }
3103
3195
 
3104
3196
  // src/layout.ts
@@ -3283,7 +3375,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
3283
3375
  groupLevel: e.group?.level
3284
3376
  }
3285
3377
  });
3286
- const ctxFor = (kind, extra = {}) => ({ def, measure, locale, kind, width, previous, ...extra });
3378
+ const ctxFor = (kind, extra = {}) => ({ def, measure, locale, kind, width, previous, anchors: opts.anchors, ...extra });
3287
3379
  const newPage = () => {
3288
3380
  if (pages.length >= maxPages) throw new Error(`Report stopped at ${maxPages} pages (maxPages)`);
3289
3381
  page = { number: pages.length + 1, total: 0, width: geo.width, height: geo.height, items: [] };
@@ -3298,13 +3390,17 @@ function paginate(def, data, opts, knownTotal, docHash) {
3298
3390
  previous.clear();
3299
3391
  };
3300
3392
  const place = (band, kind, scope, extra = {}, pre) => {
3301
- const r = pre ? { height: pre.height, items: shiftItems(pre.items, y) } : renderBand(band, scope, ctxFor(kind, extra), ml, y);
3393
+ const r = pre ? { height: pre.height, items: shiftItems(pre.items, y), anchors: pre.anchors?.map((a) => ({ ...a, y: a.y + y })) } : renderBand(band, scope, ctxFor(kind, extra), ml, y);
3302
3394
  for (const it of r.items) page.items.push(it);
3395
+ if (r.anchors) {
3396
+ for (const a of r.anchors) if (a.y + a.h > limit + 0.01) a.offPage = true;
3397
+ (page.anchors ?? (page.anchors = [])).push(...r.anchors);
3398
+ }
3303
3399
  y += r.height;
3304
3400
  hasBody = true;
3305
3401
  return r.height;
3306
3402
  };
3307
- const heightOf = (band, kind, scope, extra = {}) => renderBand(band, scope, ctxFor(kind, { ...extra, previous: void 0 }), 0, 0).height;
3403
+ const heightOf = (band, kind, scope, extra = {}) => renderBand(band, scope, ctxFor(kind, { ...extra, previous: void 0, anchors: false }), 0, 0).height;
3308
3404
  const renderChunk = (e, scope, extra) => {
3309
3405
  const band = e.band;
3310
3406
  const n2 = Math.max(1, Math.floor(band.columns ?? 1));
@@ -3317,7 +3413,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
3317
3413
  ml + i * (colW + gap),
3318
3414
  0
3319
3415
  ));
3320
- return { height: Math.max(...parts.map((p) => p.height)), items: parts.flatMap((p) => p.items) };
3416
+ return { height: Math.max(...parts.map((p) => p.height)), items: parts.flatMap((p) => p.items), anchors: opts.anchors ? parts.flatMap((p) => p.anchors ?? []) : void 0 };
3321
3417
  };
3322
3418
  const isTableEntry = (e) => e.kind === "detail" || e.kind === "groupHeader" || e.kind === "groupFooter";
3323
3419
  const placeColumnHeader = (e) => {
@@ -3413,13 +3509,15 @@ function paginate(def, data, opts, knownTotal, docHash) {
3413
3509
  const draw = (band, kind, top) => {
3414
3510
  if (!band) return;
3415
3511
  if (band.visible && !truthy2(evaluate(band.visible, scope))) return;
3416
- extraItems.push(...renderBand(band, scope, ctxFor(kind, { previous: void 0 }), ml, top, 0).items);
3512
+ const r = renderBand(band, scope, ctxFor(kind, { previous: void 0 }), ml, top, 0);
3513
+ extraItems.push(...r.items);
3514
+ if (r.anchors) (p.anchors ?? (p.anchors = [])).push(...r.anchors);
3417
3515
  };
3418
3516
  if (!(def.pageHeaderSkipFirst && i === 0)) draw(b.pageHeader, "pageHeader", mt);
3419
3517
  draw(b.pageFooter, "pageFooter", geo.height - mb - footerH);
3420
3518
  p.items.push(...extraItems);
3421
3519
  });
3422
- decoratePages(pages, { watermark: def.watermark, emboss: def.emboss }, geo, (p, i) => ({
3520
+ decoratePages(pages, { watermark: def.watermark, emboss: def.emboss, locale }, geo, (p, i) => ({
3423
3521
  ...allScope,
3424
3522
  rows: pageRows[i],
3425
3523
  pageRows: pageRows[i],
@@ -3818,10 +3916,10 @@ function dataToXlsx(def, data, opts = {}) {
3818
3916
  rows.forEach((row, i) => {
3819
3917
  const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1, rowIndex: i + 1 } };
3820
3918
  out.push(cols.map((c) => {
3821
- if (c.type === "text") return { v: interpolate(c.text, scope) };
3919
+ if (c.type === "text") return { v: interpolate(c.text, scope, opts.locale) };
3822
3920
  const v = evaluate(c.expr, scope);
3823
3921
  if (typeof v === "number" || v instanceof Date) return { v, s: { fmt: excelFormat(c.format) } };
3824
- return { v: v == null ? "" : formatValue(v, c.format) };
3922
+ return { v: v == null ? "" : formatValue(v, c.format, opts.locale) };
3825
3923
  }));
3826
3924
  });
3827
3925
  const widths = cols.map((c, i) => Math.min(60, Math.max(6, String(header[i].v ?? "").length * 2, c.w / MM_PER_CHAR)));
@@ -3841,7 +3939,7 @@ function dataToCsv(def, data, opts = {}) {
3841
3939
  const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1 } };
3842
3940
  lines.push(fields.map((f2) => {
3843
3941
  const v = evaluate(f2.expr, scope);
3844
- return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format));
3942
+ return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format, opts.locale));
3845
3943
  }).join(","));
3846
3944
  });
3847
3945
  return "\uFEFF" + lines.join("\r\n");
@@ -5550,8 +5648,10 @@ function createReport(definition, data = {}, opts = {}) {
5550
5648
  let rows = data;
5551
5649
  let params = { ...opts.params };
5552
5650
  let cached = null;
5651
+ let anchored = null;
5553
5652
  void checkLicense(opts.licenseKey);
5554
5653
  const layout = () => cached ?? (cached = layoutReport(def, rows, { ...opts, params }));
5654
+ const withAnchors = () => opts.anchors ? layout() : anchored ?? (anchored = layoutReport(def, rows, { ...opts, params, anchors: true }));
5555
5655
  const allowed = async (kind, fileName) => {
5556
5656
  if (!opts.onOutput) return true;
5557
5657
  const l = layout();
@@ -5565,12 +5665,16 @@ function createReport(definition, data = {}, opts = {}) {
5565
5665
  layout,
5566
5666
  async ready() {
5567
5667
  await ensureFonts(def);
5568
- cached = null;
5668
+ cached = anchored = null;
5569
5669
  return layout();
5570
5670
  },
5671
+ anchors(prefix = "") {
5672
+ const l = withAnchors();
5673
+ return l.pages.flatMap((p, i) => (p.anchors ?? []).filter((a) => a.id.startsWith(prefix)).map((a) => ({ ...a, page: i + 1, pageWidth: p.width, pageHeight: p.height })));
5674
+ },
5571
5675
  setData(d) {
5572
5676
  rows = d;
5573
- cached = null;
5677
+ cached = anchored = null;
5574
5678
  },
5575
5679
  fingerprint() {
5576
5680
  const l = layout();
@@ -5578,11 +5682,11 @@ function createReport(definition, data = {}, opts = {}) {
5578
5682
  },
5579
5683
  setParams(p) {
5580
5684
  params = { ...p };
5581
- cached = null;
5685
+ cached = anchored = null;
5582
5686
  },
5583
5687
  setDefinition(d) {
5584
5688
  def = d;
5585
- cached = null;
5689
+ cached = anchored = null;
5586
5690
  },
5587
5691
  async print(o = {}) {
5588
5692
  if (!await allowed("print")) return false;
@@ -5608,7 +5712,7 @@ function createReport(definition, data = {}, opts = {}) {
5608
5712
  },
5609
5713
  async toXlsx(mode = "form") {
5610
5714
  if (!await allowed(mode === "data" ? "xlsx-data" : "xlsx")) return null;
5611
- const bytes = mode === "data" ? dataToXlsx(def, rows, { params }) : pagesToXlsx(layout(), def);
5715
+ const bytes = mode === "data" ? dataToXlsx(def, rows, { params, locale: opts.locale }) : pagesToXlsx(layout(), def);
5612
5716
  return new Blob([bytes], { type: MIME.xlsx });
5613
5717
  },
5614
5718
  async toHwpx() {
@@ -5617,7 +5721,7 @@ function createReport(definition, data = {}, opts = {}) {
5617
5721
  },
5618
5722
  async toCsv() {
5619
5723
  if (!await allowed("csv")) return null;
5620
- return new Blob([dataToCsv(def, rows, { params })], { type: MIME.csv });
5724
+ return new Blob([dataToCsv(def, rows, { params, locale: opts.locale })], { type: MIME.csv });
5621
5725
  },
5622
5726
  async download(kind, fileName) {
5623
5727
  const base2 = safeName(fileName ?? def.name ?? "report");
@@ -5719,7 +5823,7 @@ var PAGE_GAP = 16;
5719
5823
  function paramInput(name, p, value, t) {
5720
5824
  const id = `fr-p-${name.replace(/[^\w-]/g, "_")}`;
5721
5825
  const label = `<label for="${id}">${escapeHtml(p.label ?? name)}${p.required ? ' <b aria-hidden="true">*</b>' : ""}</label>`;
5722
- const v = value == null ? "" : value instanceof Date ? value.toISOString().slice(0, 10) : String(value);
5826
+ const v = value == null ? "" : value instanceof Date ? formatDate(value, "yyyy-MM-dd") : String(value);
5723
5827
  const req = p.required ? " required" : "";
5724
5828
  if (p.type === "select" || p.options) {
5725
5829
  return `<div class="fr-param">${label}<select id="${id}" name="${escapeHtml(name)}"${req}>${(p.options ?? []).map((o) => `<option value="${escapeHtml(String(o.value))}"${String(o.value) === v ? " selected" : ""}>${escapeHtml(String(o.label ?? o.value))}</option>`).join("")}</select></div>`;
@@ -5784,9 +5888,10 @@ function createViewer(el, options) {
5784
5888
  const toggle = $(".fr-params-toggle");
5785
5889
  const menuBtn = $('[data-act="menu"]');
5786
5890
  const menuList = $(".fr-menu-list");
5891
+ const overlayText = $(".fr-overlay-text");
5787
5892
  const busy = (text) => {
5788
5893
  overlay.hidden = text === null;
5789
- $(".fr-overlay-text").textContent = text ?? "";
5894
+ overlayText.textContent = text ?? "";
5790
5895
  };
5791
5896
  const showParams = (open) => {
5792
5897
  if (!form || !toggle) return;
@@ -5838,6 +5943,7 @@ function createViewer(el, options) {
5838
5943
  const holder = document.createElement("div");
5839
5944
  holder.className = "fr-page";
5840
5945
  holder.dataset.index = String(i);
5946
+ holder.dataset.page = String(i + 1);
5841
5947
  holder.setAttribute("aria-label", `${t.page} ${i + 1}`);
5842
5948
  frag.appendChild(holder);
5843
5949
  });
@@ -5847,7 +5953,11 @@ function createViewer(el, options) {
5847
5953
  for (const e of entries) if (e.isIntersecting) drawPage(e.target);
5848
5954
  updateCurrent();
5849
5955
  }, { root: body, rootMargin: "600px 0px" }) : null;
5850
- pagesEl.querySelectorAll(".fr-page").forEach((p, i) => observer ? observer.observe(p) : i < 3 && drawPage(p));
5956
+ const holders = pagesEl.querySelectorAll(".fr-page");
5957
+ holders.forEach((p, i) => observer ? observer.observe(p) : i < 3 && drawPage(p));
5958
+ const pageH = result.height * MM_PX * scale + PAGE_GAP;
5959
+ const from = Math.min(holders.length - 1, Math.max(0, Math.floor(body.scrollTop / pageH)));
5960
+ for (let i = from; i < Math.min(holders.length, from + 1 + Math.ceil(body.clientHeight / pageH)); i++) drawPage(holders[i]);
5851
5961
  if (totalEl) totalEl.textContent = `/ ${result.pages.length}`;
5852
5962
  lastQuery = "";
5853
5963
  hits = [];
@@ -5897,35 +6007,57 @@ function createViewer(el, options) {
5897
6007
  body.scrollTop = ratio * body.scrollHeight;
5898
6008
  };
5899
6009
  let runSeq = 0;
5900
- const run = async () => {
5901
- const seq2 = ++runSeq;
6010
+ let runs = 0;
6011
+ let latest = Promise.resolve();
6012
+ let running = false, deciding = 0;
6013
+ let outcome = null;
6014
+ const showState = () => {
6015
+ if (destroyed) return;
6016
+ el.dataset.frState = running || deciding ? "busy" : outcome ?? "busy";
6017
+ };
6018
+ const finish = (how) => {
6019
+ running = false;
6020
+ outcome = how;
6021
+ el.dataset.frRun = String(++runs);
6022
+ showState();
6023
+ };
6024
+ const runOnce = async (seq2) => {
5902
6025
  busy(t.loading);
6026
+ running = true;
6027
+ showState();
5903
6028
  let during = "data";
6029
+ const stale = () => seq2 !== runSeq;
5904
6030
  try {
5905
6031
  if (options.fetchData) {
5906
6032
  const fetched = await options.fetchData(params);
5907
- if (seq2 !== runSeq) return;
6033
+ if (stale()) return await latest;
5908
6034
  report.setData(fetched);
5909
6035
  }
5910
6036
  during = "layout";
5911
6037
  report.setParams(params);
5912
6038
  await ensureFonts(report.definition);
5913
6039
  if (destroyed) return;
6040
+ if (stale()) return await latest;
5914
6041
  result = report.layout();
5915
6042
  reported = -1;
5916
6043
  build();
6044
+ finish("ready");
5917
6045
  try {
5918
6046
  options.onReady?.({ pages: result.pages.length, rows: result.rowCount, ms: result.ms, params: { ...params } });
5919
6047
  } catch (e) {
5920
6048
  console.error(e);
5921
6049
  }
5922
6050
  } catch (e) {
6051
+ if (stale()) return await latest;
6052
+ if (destroyed) return;
5923
6053
  const err = fail(e, during);
5924
6054
  pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(err.message)}</p>`;
6055
+ finish("error");
5925
6056
  } finally {
5926
- busy(null);
6057
+ if (!stale()) busy(null);
5927
6058
  }
5928
6059
  };
6060
+ const run = () => latest = runOnce(++runSeq);
5929
6061
  const exportAs = async (kind) => {
5930
6062
  busy(t.exporting);
5931
6063
  try {
@@ -6056,14 +6188,22 @@ function createViewer(el, options) {
6056
6188
  const v = inp.value;
6057
6189
  next[inp.name] = type === "number" ? v === "" ? null : Number(v) : type === "boolean" ? v === "true" : v;
6058
6190
  });
6191
+ deciding++;
6192
+ showState();
6059
6193
  let answer;
6060
6194
  try {
6061
6195
  answer = await options.onParamsSubmit?.({ ...next });
6062
6196
  } catch (x) {
6063
6197
  fail(x, "data");
6198
+ deciding--;
6199
+ showState();
6200
+ return;
6201
+ }
6202
+ deciding--;
6203
+ if (answer === false) {
6204
+ showState();
6064
6205
  return;
6065
6206
  }
6066
- if (answer === false) return;
6067
6207
  params = answer && typeof answer === "object" ? { ...next, ...answer } : next;
6068
6208
  void run();
6069
6209
  });
@@ -6175,6 +6315,8 @@ function createViewer(el, options) {
6175
6315
  listeners.abort();
6176
6316
  el.innerHTML = "";
6177
6317
  el.classList.remove("fr-viewer");
6318
+ delete el.dataset.frState;
6319
+ delete el.dataset.frRun;
6178
6320
  }
6179
6321
  };
6180
6322
  }
@@ -6222,7 +6364,7 @@ function ensureIds(def) {
6222
6364
  for (const s of bandSlots(def)) for (const el of s.band.elements) if (el.id) all.add(el.id);
6223
6365
  const seen = /* @__PURE__ */ new Set();
6224
6366
  for (const s of bandSlots(def)) for (const el of s.band.elements) {
6225
- if (el.id && /^[\w-]{1,64}$/.test(String(el.id)) && !seen.has(el.id)) {
6367
+ if (el.id && /^[\w:.-]{1,64}$/.test(String(el.id)) && !seen.has(el.id)) {
6226
6368
  seen.add(el.id);
6227
6369
  continue;
6228
6370
  }
@@ -6340,7 +6482,13 @@ var L = {
6340
6482
  suppress: "\uAC19\uC740 \uAC12 \uBC18\uBCF5 \uC228\uAE30\uAE30",
6341
6483
  visible: "\uD45C\uC2DC \uC870\uAC74 (\uC2DD)",
6342
6484
  canGrow: "\uB0B4\uC6A9\uC5D0 \uB9DE\uCDB0 \uB298\uB9AC\uAE30",
6343
- stretch: "\uBC34\uB4DC \uB192\uC774 \uB530\uB77C\uAC00\uAE30",
6485
+ stretch: "\uB192\uC774 \uB9DE\uCD94\uAE30",
6486
+ stretchAuto: "\uAE30\uBCF8 (\uBC34\uB4DC \uBC14\uB2E5\uC5D0 \uB2FF\uC73C\uBA74 \uBC34\uB4DC \uB192\uC774)",
6487
+ stretchBand: "\uBC34\uB4DC \uB192\uC774",
6488
+ stretchRow: "\uAC19\uC740 \uC904\uC758 \uAC00\uC7A5 \uB192\uC740 \uCE78",
6489
+ stretchNone: "\uB9DE\uCD94\uC9C0 \uC54A\uC74C",
6490
+ shift: "\uC704 \uC694\uC18C\uAC00 \uB298\uBA74 \uD568\uAED8 \uB0B4\uB824\uAC00\uAE30",
6491
+ collapse: "\uC228\uC73C\uBA74 \uC790\uB9AC \uC811\uAE30 (\uC544\uB798 \uC694\uC18C\uAC00 \uC62C\uB77C\uC634)",
6344
6492
  style: "\uC2A4\uD0C0\uC77C",
6345
6493
  bold: "\uAD75\uAC8C",
6346
6494
  italic: "\uAE30\uC6B8\uC784",
@@ -6454,7 +6602,13 @@ var L = {
6454
6602
  suppress: "Hide repeated values",
6455
6603
  visible: "Visible when (expression)",
6456
6604
  canGrow: "Grow to fit content",
6457
- stretch: "Follow band height",
6605
+ stretch: "Height",
6606
+ stretchAuto: "Default (band height when touching its bottom)",
6607
+ stretchBand: "Band height",
6608
+ stretchRow: "Tallest cell on the same row",
6609
+ stretchNone: "Own height",
6610
+ shift: "Move down when an element above grows",
6611
+ collapse: "Give the space back when hidden",
6458
6612
  style: "Style",
6459
6613
  bold: "Bold",
6460
6614
  italic: "Italic",
@@ -6713,7 +6867,16 @@ function elementFields(els, t, def) {
6713
6867
  out.push({ label: t.shrink, kind: "check", get: () => !!same((e) => !!e.style?.shrinkToFit), set: (v) => styleSet("shrinkToFit")(v) });
6714
6868
  out.push({ label: t.canGrow, kind: "check", get: () => same((e) => e.canGrow !== false), set: (v) => setAll((e) => v ? delete e.canGrow : e.canGrow = false) });
6715
6869
  }
6870
+ out.push({
6871
+ label: t.stretch,
6872
+ kind: "select",
6873
+ options: [{ value: "", label: t.stretchAuto }, { value: "band", label: t.stretchBand }, { value: "row", label: t.stretchRow }, { value: "none", label: t.stretchNone }],
6874
+ get: () => same((e) => e.stretch === void 0 ? "" : e.stretch === "row" ? "row" : e.stretch ? "band" : "none"),
6875
+ set: (v) => setAll((e) => v === "row" ? e.stretch = "row" : v === "band" ? e.stretch = true : v === "none" ? e.stretch = false : delete e.stretch)
6876
+ });
6877
+ out.push({ label: t.shift, kind: "check", get: () => same((e) => e.shift !== false), set: (v) => setAll((e) => v ? delete e.shift : e.shift = false) });
6716
6878
  out.push({ label: t.visible, kind: "expr", wide: true, get: () => same((e) => e.visible ?? "") ?? "", set: (v) => setAll((e) => v ? e.visible = String(v) : delete e.visible) });
6879
+ out.push({ label: t.collapse, kind: "check", get: () => same((e) => !!e.collapse), set: (v) => setAll((e) => v ? e.collapse = true : delete e.collapse) });
6717
6880
  return out;
6718
6881
  }
6719
6882
  function bandFields(band, label, t, group, isDetail) {
@@ -7879,7 +8042,7 @@ async function renderPdf(def, data = {}, opts = {}) {
7879
8042
  return writePdf(layout.pages, { title: def.name, creator: "FreeReport", ...opts.pdf, fonts: { default: first, ...fonts } });
7880
8043
  }
7881
8044
  async function renderXlsx(def, data = {}, opts = {}) {
7882
- if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params });
8045
+ if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params, locale: opts.locale });
7883
8046
  const { layout } = await prepare2(def, data, opts);
7884
8047
  return pagesToXlsx(layout, def);
7885
8048
  }