@freereportjs/core 0.10.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/cdn/freereport.esm.js +62 -62
- package/dist/cdn/freereport.min.js +58 -58
- package/dist/index.d.ts +15 -4
- package/dist/index.js +134 -59
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -76,14 +76,21 @@ interface ElementBase {
|
|
|
76
76
|
canGrow?: boolean;
|
|
77
77
|
/**
|
|
78
78
|
* Follow the band's height when it grows. Default: true for elements that touch the bottom
|
|
79
|
-
* of the band, so cells of a table row stay the same height.
|
|
79
|
+
* of the band, so cells of a table row stay the same height. 'row': take the height of the
|
|
80
|
+
* tallest element on the same row (same y and h), so a label cell grows with its content cell.
|
|
80
81
|
*/
|
|
81
|
-
stretch?: boolean;
|
|
82
|
+
stretch?: boolean | 'row';
|
|
82
83
|
/**
|
|
83
84
|
* Move down when an element above it grows (canGrow), by as much as that element grew, so
|
|
84
|
-
* long text pushes the rest of the band down instead of overlapping it.
|
|
85
|
+
* long text pushes the rest of the band down instead of overlapping it. Only elements that
|
|
86
|
+
* overlap it horizontally move. Default true.
|
|
85
87
|
*/
|
|
86
88
|
shift?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* When `visible` is false, give its space back: the elements below it move up into its place
|
|
91
|
+
* and the band shrinks. Default false (the space stays empty).
|
|
92
|
+
*/
|
|
93
|
+
collapse?: boolean;
|
|
87
94
|
/** Excel export: column name in "data" mode; ignored elsewhere. */
|
|
88
95
|
name?: string;
|
|
89
96
|
}
|
|
@@ -511,6 +518,8 @@ interface ElementAnchor {
|
|
|
511
518
|
h: number;
|
|
512
519
|
/** Its `visible` expression was false: the box it would have used. */
|
|
513
520
|
hidden?: boolean;
|
|
521
|
+
/** It ends below the page's printable area (a band that grew past the page). */
|
|
522
|
+
offPage?: boolean;
|
|
514
523
|
}
|
|
515
524
|
interface LayoutResult {
|
|
516
525
|
pages: ReportPage[];
|
|
@@ -921,10 +930,12 @@ declare function pagesToXlsx(layout: LayoutResult, def: ReportDefinition, opts?:
|
|
|
921
930
|
declare function dataToXlsx(def: ReportDefinition, data: ReportData, opts?: {
|
|
922
931
|
params?: Record<string, unknown>;
|
|
923
932
|
sheetName?: string;
|
|
933
|
+
locale?: string;
|
|
924
934
|
}): Uint8Array;
|
|
925
935
|
/** CSV of the same columns as `dataToXlsx` (UTF-8 with BOM so Excel opens Korean correctly). */
|
|
926
936
|
declare function dataToCsv(def: ReportDefinition, data: ReportData, opts?: {
|
|
927
937
|
params?: Record<string, unknown>;
|
|
938
|
+
locale?: string;
|
|
928
939
|
}): string;
|
|
929
940
|
|
|
930
941
|
/** Export laid-out pages as an HWPX document (한/글 2018+, 한컴오피스 뷰어). */
|
|
@@ -1185,7 +1196,7 @@ declare function verifyLicense(key: string, host: string, opts?: {
|
|
|
1185
1196
|
declare function setLicenseKey(key: string): void;
|
|
1186
1197
|
|
|
1187
1198
|
/** FreeReport version (kept in sync with releases.json by scripts/release.mjs). */
|
|
1188
|
-
declare const version = "0.10.
|
|
1199
|
+
declare const version = "0.10.1";
|
|
1189
1200
|
|
|
1190
1201
|
type Lang = 'ko' | 'en';
|
|
1191
1202
|
|
package/dist/index.js
CHANGED
|
@@ -368,7 +368,7 @@ function formatValue(v, format, locale = "ko") {
|
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
// src/version.ts
|
|
371
|
-
var version = "0.10.
|
|
371
|
+
var version = "0.10.1";
|
|
372
372
|
var releaseDate = "2026-10-19";
|
|
373
373
|
|
|
374
374
|
// src/license.ts
|
|
@@ -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
|
});
|
|
@@ -1353,7 +1353,7 @@ function textSvg(t, clipId) {
|
|
|
1353
1353
|
}
|
|
1354
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);
|
|
1355
1355
|
const transform = t.rotate ? ` transform="rotate(${n(t.rotate)} ${n(t.x + t.w / 2)} ${n(t.y + t.h / 2)})"` : "";
|
|
1356
|
-
const hooks = sourceAttrs(t) + (t.overflow ? ' data-fr-overflow="1"' : "");
|
|
1356
|
+
const hooks = sourceAttrs(t) + (t.overflow && t.source?.band !== "watermark" ? ' data-fr-overflow="1"' : "");
|
|
1357
1357
|
if (!overflow) return { defs: "", body: `<g ${attrs}${hooks}${transform}${opacityAttr(t.opacity)}>${body}</g>` };
|
|
1358
1358
|
const id = clipId();
|
|
1359
1359
|
return {
|
|
@@ -1408,7 +1408,7 @@ function pageToSvg(page, opts = {}) {
|
|
|
1408
1408
|
const r = itemSvg(it, clipId);
|
|
1409
1409
|
defs += r.defs;
|
|
1410
1410
|
const hooks = it.kind === "text" ? "" : sourceAttrs(it);
|
|
1411
|
-
body += hooks
|
|
1411
|
+
body += !hooks || !r.body ? r.body : r.body.indexOf("<", 1) < 0 ? r.body.replace(/^<(\w+)/, (_, tag) => `<${tag}${hooks}`) : `<g${hooks}>${r.body}</g>`;
|
|
1412
1412
|
}
|
|
1413
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"`;
|
|
1414
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>`;
|
|
@@ -2548,7 +2548,10 @@ function renderChart(el, scope, box, ctx) {
|
|
|
2548
2548
|
const labelStyle = { ...style, padding: [0, 0, 0, 0], letterSpacing: 0, border: {} };
|
|
2549
2549
|
const lh = fontSize * PT * 1.3;
|
|
2550
2550
|
const items = [];
|
|
2551
|
-
const label = (text, x2, y2, w2, align, bold = false, color = "#444444") =>
|
|
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
|
+
);
|
|
2552
2555
|
let { x, y, w, h } = box;
|
|
2553
2556
|
if (el.title) {
|
|
2554
2557
|
label(el.title, x, y, w, "center", true, style.color);
|
|
@@ -2952,7 +2955,7 @@ function textItem(x, y, w, h, lines, font, st, measure) {
|
|
|
2952
2955
|
else if (st.align === "right") lx = x + w - pr - width;
|
|
2953
2956
|
return { text, x: lx, y: top + i * lineH + (lineH - font.size * PT) / 2 + font.size * PT * ASCENT, width };
|
|
2954
2957
|
});
|
|
2955
|
-
const overflow = total >
|
|
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);
|
|
2956
2959
|
return {
|
|
2957
2960
|
kind: "text",
|
|
2958
2961
|
x,
|
|
@@ -3036,28 +3039,48 @@ function barcodeItems(el, x, y, w, h, scope, ctx) {
|
|
|
3036
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)];
|
|
3037
3040
|
}
|
|
3038
3041
|
}
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
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;
|
|
3043
3050
|
break;
|
|
3044
3051
|
}
|
|
3045
|
-
if (!
|
|
3046
|
-
const
|
|
3047
|
-
const
|
|
3048
|
-
const
|
|
3049
|
-
const order = [...prepared].sort((a, b) => top(a) - top(b));
|
|
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);
|
|
3050
3056
|
for (const p of order) {
|
|
3051
3057
|
if (p.el.shift === false) continue;
|
|
3052
|
-
|
|
3058
|
+
const pt = topOf(p.el), pl = leftOf(p.el), pr = rightOf(p.el);
|
|
3059
|
+
let nearest3 = null;
|
|
3060
|
+
let floor = -Infinity, pushed = -Infinity;
|
|
3053
3061
|
for (const q of order) {
|
|
3054
|
-
if (
|
|
3055
|
-
if (
|
|
3056
|
-
|
|
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);
|
|
3057
3082
|
}
|
|
3058
|
-
if (by > 0) out.set(p, by);
|
|
3059
3083
|
}
|
|
3060
|
-
return out;
|
|
3061
3084
|
}
|
|
3062
3085
|
function addAnchor(out, el, ctx, left, top, y, h, hidden) {
|
|
3063
3086
|
if (!el.id) return;
|
|
@@ -3075,29 +3098,47 @@ function addAnchor(out, el, ctx, left, top, y, h, hidden) {
|
|
|
3075
3098
|
}
|
|
3076
3099
|
function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
3077
3100
|
const bandGrow = band.canGrow ?? (ctx.kind === "detail" || ctx.kind === "groupHeader" || ctx.kind === "groupFooter" || ctx.kind === "summary" || ctx.kind === "title");
|
|
3078
|
-
const
|
|
3101
|
+
const slots = [];
|
|
3079
3102
|
const anchors = ctx.anchors && !ctx.blank ? [] : void 0;
|
|
3103
|
+
let collapsed = false, rows = false;
|
|
3080
3104
|
for (const el of band.elements) {
|
|
3081
3105
|
if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope))) {
|
|
3082
|
-
if (
|
|
3106
|
+
if (el.collapse) collapsed = true;
|
|
3107
|
+
slots.push({ el, style: resolveStyle(ctx.def, el), needed: el.h, hidden: true, collapsed: !!el.collapse });
|
|
3083
3108
|
continue;
|
|
3084
3109
|
}
|
|
3085
|
-
|
|
3110
|
+
if (el.stretch === "row") rows = true;
|
|
3111
|
+
slots.push(prepare(el, scope, ctx, bandGrow));
|
|
3086
3112
|
}
|
|
3087
|
-
|
|
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;
|
|
3088
3116
|
let height = Math.max(band.height, minHeight);
|
|
3089
|
-
|
|
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);
|
|
3090
3125
|
const items = [];
|
|
3091
3126
|
const bg = ctx.background ?? band.background;
|
|
3092
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 } });
|
|
3093
|
-
for (const p of
|
|
3128
|
+
for (const p of slots) {
|
|
3094
3129
|
const el = p.el;
|
|
3095
|
-
const elY =
|
|
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
|
+
}
|
|
3096
3135
|
const touchesBottom = el.y + el.h >= band.height - 0.01;
|
|
3136
|
+
const follows = el.stretch === "row" ? touchesBottom : el.stretch ?? touchesBottom;
|
|
3097
3137
|
let h = p.needed;
|
|
3098
|
-
if (
|
|
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;
|
|
3099
3140
|
const x = left + el.x, y = top + elY;
|
|
3100
|
-
if (anchors) addAnchor(anchors, el, ctx, left, top, elY,
|
|
3141
|
+
if (anchors) addAnchor(anchors, el, ctx, left, top, elY, h);
|
|
3101
3142
|
switch (el.type) {
|
|
3102
3143
|
case "text":
|
|
3103
3144
|
case "field": {
|
|
@@ -3112,8 +3153,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
|
3112
3153
|
break;
|
|
3113
3154
|
}
|
|
3114
3155
|
case "line": {
|
|
3115
|
-
|
|
3116
|
-
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 } });
|
|
3117
3157
|
break;
|
|
3118
3158
|
}
|
|
3119
3159
|
case "rect": {
|
|
@@ -3352,7 +3392,10 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3352
3392
|
const place = (band, kind, scope, extra = {}, pre) => {
|
|
3353
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);
|
|
3354
3394
|
for (const it of r.items) page.items.push(it);
|
|
3355
|
-
if (r.anchors)
|
|
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
|
+
}
|
|
3356
3399
|
y += r.height;
|
|
3357
3400
|
hasBody = true;
|
|
3358
3401
|
return r.height;
|
|
@@ -3474,7 +3517,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3474
3517
|
draw(b.pageFooter, "pageFooter", geo.height - mb - footerH);
|
|
3475
3518
|
p.items.push(...extraItems);
|
|
3476
3519
|
});
|
|
3477
|
-
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss }, geo, (p, i) => ({
|
|
3520
|
+
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss, locale }, geo, (p, i) => ({
|
|
3478
3521
|
...allScope,
|
|
3479
3522
|
rows: pageRows[i],
|
|
3480
3523
|
pageRows: pageRows[i],
|
|
@@ -3873,10 +3916,10 @@ function dataToXlsx(def, data, opts = {}) {
|
|
|
3873
3916
|
rows.forEach((row, i) => {
|
|
3874
3917
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1, rowIndex: i + 1 } };
|
|
3875
3918
|
out.push(cols.map((c) => {
|
|
3876
|
-
if (c.type === "text") return { v: interpolate(c.text, scope) };
|
|
3919
|
+
if (c.type === "text") return { v: interpolate(c.text, scope, opts.locale) };
|
|
3877
3920
|
const v = evaluate(c.expr, scope);
|
|
3878
3921
|
if (typeof v === "number" || v instanceof Date) return { v, s: { fmt: excelFormat(c.format) } };
|
|
3879
|
-
return { v: v == null ? "" : formatValue(v, c.format) };
|
|
3922
|
+
return { v: v == null ? "" : formatValue(v, c.format, opts.locale) };
|
|
3880
3923
|
}));
|
|
3881
3924
|
});
|
|
3882
3925
|
const widths = cols.map((c, i) => Math.min(60, Math.max(6, String(header[i].v ?? "").length * 2, c.w / MM_PER_CHAR)));
|
|
@@ -3896,7 +3939,7 @@ function dataToCsv(def, data, opts = {}) {
|
|
|
3896
3939
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1 } };
|
|
3897
3940
|
lines.push(fields.map((f2) => {
|
|
3898
3941
|
const v = evaluate(f2.expr, scope);
|
|
3899
|
-
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));
|
|
3900
3943
|
}).join(","));
|
|
3901
3944
|
});
|
|
3902
3945
|
return "\uFEFF" + lines.join("\r\n");
|
|
@@ -5669,7 +5712,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5669
5712
|
},
|
|
5670
5713
|
async toXlsx(mode = "form") {
|
|
5671
5714
|
if (!await allowed(mode === "data" ? "xlsx-data" : "xlsx")) return null;
|
|
5672
|
-
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);
|
|
5673
5716
|
return new Blob([bytes], { type: MIME.xlsx });
|
|
5674
5717
|
},
|
|
5675
5718
|
async toHwpx() {
|
|
@@ -5678,7 +5721,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5678
5721
|
},
|
|
5679
5722
|
async toCsv() {
|
|
5680
5723
|
if (!await allowed("csv")) return null;
|
|
5681
|
-
return new Blob([dataToCsv(def, rows, { params })], { type: MIME.csv });
|
|
5724
|
+
return new Blob([dataToCsv(def, rows, { params, locale: opts.locale })], { type: MIME.csv });
|
|
5682
5725
|
},
|
|
5683
5726
|
async download(kind, fileName) {
|
|
5684
5727
|
const base2 = safeName(fileName ?? def.name ?? "report");
|
|
@@ -5966,12 +6009,22 @@ function createViewer(el, options) {
|
|
|
5966
6009
|
let runSeq = 0;
|
|
5967
6010
|
let runs = 0;
|
|
5968
6011
|
let latest = Promise.resolve();
|
|
5969
|
-
|
|
5970
|
-
|
|
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();
|
|
5971
6023
|
};
|
|
5972
6024
|
const runOnce = async (seq2) => {
|
|
5973
6025
|
busy(t.loading);
|
|
5974
|
-
|
|
6026
|
+
running = true;
|
|
6027
|
+
showState();
|
|
5975
6028
|
let during = "data";
|
|
5976
6029
|
const stale = () => seq2 !== runSeq;
|
|
5977
6030
|
try {
|
|
@@ -5988,8 +6041,7 @@ function createViewer(el, options) {
|
|
|
5988
6041
|
result = report.layout();
|
|
5989
6042
|
reported = -1;
|
|
5990
6043
|
build();
|
|
5991
|
-
|
|
5992
|
-
setState("ready");
|
|
6044
|
+
finish("ready");
|
|
5993
6045
|
try {
|
|
5994
6046
|
options.onReady?.({ pages: result.pages.length, rows: result.rowCount, ms: result.ms, params: { ...params } });
|
|
5995
6047
|
} catch (e) {
|
|
@@ -5997,10 +6049,10 @@ function createViewer(el, options) {
|
|
|
5997
6049
|
}
|
|
5998
6050
|
} catch (e) {
|
|
5999
6051
|
if (stale()) return await latest;
|
|
6052
|
+
if (destroyed) return;
|
|
6000
6053
|
const err = fail(e, during);
|
|
6001
6054
|
pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(err.message)}</p>`;
|
|
6002
|
-
|
|
6003
|
-
setState("error");
|
|
6055
|
+
finish("error");
|
|
6004
6056
|
} finally {
|
|
6005
6057
|
if (!stale()) busy(null);
|
|
6006
6058
|
}
|
|
@@ -6136,18 +6188,20 @@ function createViewer(el, options) {
|
|
|
6136
6188
|
const v = inp.value;
|
|
6137
6189
|
next[inp.name] = type === "number" ? v === "" ? null : Number(v) : type === "boolean" ? v === "true" : v;
|
|
6138
6190
|
});
|
|
6139
|
-
|
|
6140
|
-
|
|
6191
|
+
deciding++;
|
|
6192
|
+
showState();
|
|
6141
6193
|
let answer;
|
|
6142
6194
|
try {
|
|
6143
6195
|
answer = await options.onParamsSubmit?.({ ...next });
|
|
6144
6196
|
} catch (x) {
|
|
6145
6197
|
fail(x, "data");
|
|
6146
|
-
|
|
6198
|
+
deciding--;
|
|
6199
|
+
showState();
|
|
6147
6200
|
return;
|
|
6148
6201
|
}
|
|
6202
|
+
deciding--;
|
|
6149
6203
|
if (answer === false) {
|
|
6150
|
-
|
|
6204
|
+
showState();
|
|
6151
6205
|
return;
|
|
6152
6206
|
}
|
|
6153
6207
|
params = answer && typeof answer === "object" ? { ...next, ...answer } : next;
|
|
@@ -6428,7 +6482,13 @@ var L = {
|
|
|
6428
6482
|
suppress: "\uAC19\uC740 \uAC12 \uBC18\uBCF5 \uC228\uAE30\uAE30",
|
|
6429
6483
|
visible: "\uD45C\uC2DC \uC870\uAC74 (\uC2DD)",
|
|
6430
6484
|
canGrow: "\uB0B4\uC6A9\uC5D0 \uB9DE\uCDB0 \uB298\uB9AC\uAE30",
|
|
6431
|
-
stretch: "\
|
|
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)",
|
|
6432
6492
|
style: "\uC2A4\uD0C0\uC77C",
|
|
6433
6493
|
bold: "\uAD75\uAC8C",
|
|
6434
6494
|
italic: "\uAE30\uC6B8\uC784",
|
|
@@ -6542,7 +6602,13 @@ var L = {
|
|
|
6542
6602
|
suppress: "Hide repeated values",
|
|
6543
6603
|
visible: "Visible when (expression)",
|
|
6544
6604
|
canGrow: "Grow to fit content",
|
|
6545
|
-
stretch: "
|
|
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",
|
|
6546
6612
|
style: "Style",
|
|
6547
6613
|
bold: "Bold",
|
|
6548
6614
|
italic: "Italic",
|
|
@@ -6801,7 +6867,16 @@ function elementFields(els, t, def) {
|
|
|
6801
6867
|
out.push({ label: t.shrink, kind: "check", get: () => !!same((e) => !!e.style?.shrinkToFit), set: (v) => styleSet("shrinkToFit")(v) });
|
|
6802
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) });
|
|
6803
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) });
|
|
6804
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) });
|
|
6805
6880
|
return out;
|
|
6806
6881
|
}
|
|
6807
6882
|
function bandFields(band, label, t, group, isDetail) {
|
|
@@ -7967,7 +8042,7 @@ async function renderPdf(def, data = {}, opts = {}) {
|
|
|
7967
8042
|
return writePdf(layout.pages, { title: def.name, creator: "FreeReport", ...opts.pdf, fonts: { default: first, ...fonts } });
|
|
7968
8043
|
}
|
|
7969
8044
|
async function renderXlsx(def, data = {}, opts = {}) {
|
|
7970
|
-
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 });
|
|
7971
8046
|
const { layout } = await prepare2(def, data, opts);
|
|
7972
8047
|
return pagesToXlsx(layout, def);
|
|
7973
8048
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freereportjs/core",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "FreeReport — a web reporting engine (free for personal use): banded report designer, paged viewer, printing, PDF with embedded Korean fonts, Excel export.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|