@freereportjs/core 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +138 -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.2";
|
|
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.2";
|
|
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,52 @@ 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;
|
|
3061
|
+
const above = [];
|
|
3053
3062
|
for (const q of order) {
|
|
3054
|
-
if (
|
|
3055
|
-
if (
|
|
3056
|
-
|
|
3063
|
+
if (topOf(q.el) >= pt) break;
|
|
3064
|
+
if (bottomOf(q.el) > pt + 0.01 || leftOf(q.el) >= pr - 0.01 || pl >= rightOf(q.el) - 0.01) continue;
|
|
3065
|
+
above.push(q);
|
|
3066
|
+
if (!nearest3 || bottomOf(q.el) > bottomOf(nearest3.el) + 0.01) nearest3 = q;
|
|
3067
|
+
}
|
|
3068
|
+
for (const q of above) {
|
|
3069
|
+
if (q.collapsed) continue;
|
|
3070
|
+
const by2 = (off.get(q) ?? 0) + growth(q);
|
|
3071
|
+
if (by2 > 0 && !above.some((c) => c.collapsed && topOf(c.el) >= bottomOf(q.el) - 0.01)) pushed = Math.max(pushed, by2);
|
|
3072
|
+
floor = Math.max(floor, bottomOf(q.el) + by2 - pt);
|
|
3073
|
+
}
|
|
3074
|
+
if (!nearest3) continue;
|
|
3075
|
+
const base2 = nearest3.collapsed ? topOf(nearest3.el) + (off.get(nearest3) ?? 0) - pt : (off.get(nearest3) ?? 0) + growth(nearest3);
|
|
3076
|
+
const by = Math.max(base2, pushed, floor);
|
|
3077
|
+
if (Math.abs(by) > 1e-9) off.set(p, by);
|
|
3078
|
+
}
|
|
3079
|
+
return off;
|
|
3080
|
+
}
|
|
3081
|
+
function stretchRows(slots) {
|
|
3082
|
+
for (const s of slots) {
|
|
3083
|
+
if (s.el.stretch !== "row" || s.hidden) continue;
|
|
3084
|
+
for (const o of slots) {
|
|
3085
|
+
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
3086
|
}
|
|
3058
|
-
if (by > 0) out.set(p, by);
|
|
3059
3087
|
}
|
|
3060
|
-
return out;
|
|
3061
3088
|
}
|
|
3062
3089
|
function addAnchor(out, el, ctx, left, top, y, h, hidden) {
|
|
3063
3090
|
if (!el.id) return;
|
|
@@ -3075,29 +3102,47 @@ function addAnchor(out, el, ctx, left, top, y, h, hidden) {
|
|
|
3075
3102
|
}
|
|
3076
3103
|
function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
3077
3104
|
const bandGrow = band.canGrow ?? (ctx.kind === "detail" || ctx.kind === "groupHeader" || ctx.kind === "groupFooter" || ctx.kind === "summary" || ctx.kind === "title");
|
|
3078
|
-
const
|
|
3105
|
+
const slots = [];
|
|
3079
3106
|
const anchors = ctx.anchors && !ctx.blank ? [] : void 0;
|
|
3107
|
+
let collapsed = false, rows = false;
|
|
3080
3108
|
for (const el of band.elements) {
|
|
3081
3109
|
if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope))) {
|
|
3082
|
-
if (
|
|
3110
|
+
if (el.collapse) collapsed = true;
|
|
3111
|
+
slots.push({ el, style: resolveStyle(ctx.def, el), needed: el.h, hidden: true, collapsed: !!el.collapse });
|
|
3083
3112
|
continue;
|
|
3084
3113
|
}
|
|
3085
|
-
|
|
3114
|
+
if (el.stretch === "row") rows = true;
|
|
3115
|
+
slots.push(prepare(el, scope, ctx, bandGrow));
|
|
3086
3116
|
}
|
|
3087
|
-
|
|
3117
|
+
if (rows) stretchRows(slots);
|
|
3118
|
+
const shift2 = shifts(slots);
|
|
3119
|
+
const yOf = (s) => shift2 ? s.el.y + (shift2.get(s) ?? 0) : s.el.y;
|
|
3088
3120
|
let height = Math.max(band.height, minHeight);
|
|
3089
|
-
|
|
3121
|
+
if (collapsed) {
|
|
3122
|
+
let lowest = 0, lowestNew = 0;
|
|
3123
|
+
for (const s of slots) {
|
|
3124
|
+
lowest = Math.max(lowest, bottomOf(s.el));
|
|
3125
|
+
if (!s.hidden) lowestNew = Math.max(lowestNew, yOf(s) + Math.max(s.needed, 0));
|
|
3126
|
+
}
|
|
3127
|
+
height = Math.max(minHeight, lowestNew + Math.max(0, band.height - lowest));
|
|
3128
|
+
} else for (const s of slots) if (!s.hidden) height = Math.max(height, yOf(s) + s.needed);
|
|
3090
3129
|
const items = [];
|
|
3091
3130
|
const bg = ctx.background ?? band.background;
|
|
3092
3131
|
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
|
|
3132
|
+
for (const p of slots) {
|
|
3094
3133
|
const el = p.el;
|
|
3095
|
-
const elY =
|
|
3134
|
+
const elY = yOf(p);
|
|
3135
|
+
if (p.hidden) {
|
|
3136
|
+
if (anchors) addAnchor(anchors, el, ctx, left, top, elY, el.h, true);
|
|
3137
|
+
continue;
|
|
3138
|
+
}
|
|
3096
3139
|
const touchesBottom = el.y + el.h >= band.height - 0.01;
|
|
3140
|
+
const follows = el.stretch === "row" ? touchesBottom : el.stretch ?? touchesBottom;
|
|
3097
3141
|
let h = p.needed;
|
|
3098
|
-
if (
|
|
3142
|
+
if (follows && el.type !== "line" && el.type !== "table" && el.type !== "crosstab") h = Math.max(h, height - elY);
|
|
3143
|
+
if (el.type === "line") h = el.h === 0 ? 0 : el.stretch === "row" ? p.needed : touchesBottom ? Math.max(el.h, height - elY) : el.h;
|
|
3099
3144
|
const x = left + el.x, y = top + elY;
|
|
3100
|
-
if (anchors) addAnchor(anchors, el, ctx, left, top, elY,
|
|
3145
|
+
if (anchors) addAnchor(anchors, el, ctx, left, top, elY, h);
|
|
3101
3146
|
switch (el.type) {
|
|
3102
3147
|
case "text":
|
|
3103
3148
|
case "field": {
|
|
@@ -3112,8 +3157,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
|
3112
3157
|
break;
|
|
3113
3158
|
}
|
|
3114
3159
|
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 } });
|
|
3160
|
+
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
3161
|
break;
|
|
3118
3162
|
}
|
|
3119
3163
|
case "rect": {
|
|
@@ -3352,7 +3396,10 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3352
3396
|
const place = (band, kind, scope, extra = {}, pre) => {
|
|
3353
3397
|
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
3398
|
for (const it of r.items) page.items.push(it);
|
|
3355
|
-
if (r.anchors)
|
|
3399
|
+
if (r.anchors) {
|
|
3400
|
+
for (const a of r.anchors) if (a.y + a.h > limit + 0.01) a.offPage = true;
|
|
3401
|
+
(page.anchors ?? (page.anchors = [])).push(...r.anchors);
|
|
3402
|
+
}
|
|
3356
3403
|
y += r.height;
|
|
3357
3404
|
hasBody = true;
|
|
3358
3405
|
return r.height;
|
|
@@ -3474,7 +3521,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3474
3521
|
draw(b.pageFooter, "pageFooter", geo.height - mb - footerH);
|
|
3475
3522
|
p.items.push(...extraItems);
|
|
3476
3523
|
});
|
|
3477
|
-
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss }, geo, (p, i) => ({
|
|
3524
|
+
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss, locale }, geo, (p, i) => ({
|
|
3478
3525
|
...allScope,
|
|
3479
3526
|
rows: pageRows[i],
|
|
3480
3527
|
pageRows: pageRows[i],
|
|
@@ -3873,10 +3920,10 @@ function dataToXlsx(def, data, opts = {}) {
|
|
|
3873
3920
|
rows.forEach((row, i) => {
|
|
3874
3921
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1, rowIndex: i + 1 } };
|
|
3875
3922
|
out.push(cols.map((c) => {
|
|
3876
|
-
if (c.type === "text") return { v: interpolate(c.text, scope) };
|
|
3923
|
+
if (c.type === "text") return { v: interpolate(c.text, scope, opts.locale) };
|
|
3877
3924
|
const v = evaluate(c.expr, scope);
|
|
3878
3925
|
if (typeof v === "number" || v instanceof Date) return { v, s: { fmt: excelFormat(c.format) } };
|
|
3879
|
-
return { v: v == null ? "" : formatValue(v, c.format) };
|
|
3926
|
+
return { v: v == null ? "" : formatValue(v, c.format, opts.locale) };
|
|
3880
3927
|
}));
|
|
3881
3928
|
});
|
|
3882
3929
|
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 +3943,7 @@ function dataToCsv(def, data, opts = {}) {
|
|
|
3896
3943
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1 } };
|
|
3897
3944
|
lines.push(fields.map((f2) => {
|
|
3898
3945
|
const v = evaluate(f2.expr, scope);
|
|
3899
|
-
return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format));
|
|
3946
|
+
return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format, opts.locale));
|
|
3900
3947
|
}).join(","));
|
|
3901
3948
|
});
|
|
3902
3949
|
return "\uFEFF" + lines.join("\r\n");
|
|
@@ -5669,7 +5716,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5669
5716
|
},
|
|
5670
5717
|
async toXlsx(mode = "form") {
|
|
5671
5718
|
if (!await allowed(mode === "data" ? "xlsx-data" : "xlsx")) return null;
|
|
5672
|
-
const bytes = mode === "data" ? dataToXlsx(def, rows, { params }) : pagesToXlsx(layout(), def);
|
|
5719
|
+
const bytes = mode === "data" ? dataToXlsx(def, rows, { params, locale: opts.locale }) : pagesToXlsx(layout(), def);
|
|
5673
5720
|
return new Blob([bytes], { type: MIME.xlsx });
|
|
5674
5721
|
},
|
|
5675
5722
|
async toHwpx() {
|
|
@@ -5678,7 +5725,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5678
5725
|
},
|
|
5679
5726
|
async toCsv() {
|
|
5680
5727
|
if (!await allowed("csv")) return null;
|
|
5681
|
-
return new Blob([dataToCsv(def, rows, { params })], { type: MIME.csv });
|
|
5728
|
+
return new Blob([dataToCsv(def, rows, { params, locale: opts.locale })], { type: MIME.csv });
|
|
5682
5729
|
},
|
|
5683
5730
|
async download(kind, fileName) {
|
|
5684
5731
|
const base2 = safeName(fileName ?? def.name ?? "report");
|
|
@@ -5966,12 +6013,22 @@ function createViewer(el, options) {
|
|
|
5966
6013
|
let runSeq = 0;
|
|
5967
6014
|
let runs = 0;
|
|
5968
6015
|
let latest = Promise.resolve();
|
|
5969
|
-
|
|
5970
|
-
|
|
6016
|
+
let running = false, deciding = 0;
|
|
6017
|
+
let outcome = null;
|
|
6018
|
+
const showState = () => {
|
|
6019
|
+
if (destroyed) return;
|
|
6020
|
+
el.dataset.frState = running || deciding ? "busy" : outcome ?? "busy";
|
|
6021
|
+
};
|
|
6022
|
+
const finish = (how) => {
|
|
6023
|
+
running = false;
|
|
6024
|
+
outcome = how;
|
|
6025
|
+
el.dataset.frRun = String(++runs);
|
|
6026
|
+
showState();
|
|
5971
6027
|
};
|
|
5972
6028
|
const runOnce = async (seq2) => {
|
|
5973
6029
|
busy(t.loading);
|
|
5974
|
-
|
|
6030
|
+
running = true;
|
|
6031
|
+
showState();
|
|
5975
6032
|
let during = "data";
|
|
5976
6033
|
const stale = () => seq2 !== runSeq;
|
|
5977
6034
|
try {
|
|
@@ -5988,8 +6045,7 @@ function createViewer(el, options) {
|
|
|
5988
6045
|
result = report.layout();
|
|
5989
6046
|
reported = -1;
|
|
5990
6047
|
build();
|
|
5991
|
-
|
|
5992
|
-
setState("ready");
|
|
6048
|
+
finish("ready");
|
|
5993
6049
|
try {
|
|
5994
6050
|
options.onReady?.({ pages: result.pages.length, rows: result.rowCount, ms: result.ms, params: { ...params } });
|
|
5995
6051
|
} catch (e) {
|
|
@@ -5997,10 +6053,10 @@ function createViewer(el, options) {
|
|
|
5997
6053
|
}
|
|
5998
6054
|
} catch (e) {
|
|
5999
6055
|
if (stale()) return await latest;
|
|
6056
|
+
if (destroyed) return;
|
|
6000
6057
|
const err = fail(e, during);
|
|
6001
6058
|
pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(err.message)}</p>`;
|
|
6002
|
-
|
|
6003
|
-
setState("error");
|
|
6059
|
+
finish("error");
|
|
6004
6060
|
} finally {
|
|
6005
6061
|
if (!stale()) busy(null);
|
|
6006
6062
|
}
|
|
@@ -6136,18 +6192,20 @@ function createViewer(el, options) {
|
|
|
6136
6192
|
const v = inp.value;
|
|
6137
6193
|
next[inp.name] = type === "number" ? v === "" ? null : Number(v) : type === "boolean" ? v === "true" : v;
|
|
6138
6194
|
});
|
|
6139
|
-
|
|
6140
|
-
|
|
6195
|
+
deciding++;
|
|
6196
|
+
showState();
|
|
6141
6197
|
let answer;
|
|
6142
6198
|
try {
|
|
6143
6199
|
answer = await options.onParamsSubmit?.({ ...next });
|
|
6144
6200
|
} catch (x) {
|
|
6145
6201
|
fail(x, "data");
|
|
6146
|
-
|
|
6202
|
+
deciding--;
|
|
6203
|
+
showState();
|
|
6147
6204
|
return;
|
|
6148
6205
|
}
|
|
6206
|
+
deciding--;
|
|
6149
6207
|
if (answer === false) {
|
|
6150
|
-
|
|
6208
|
+
showState();
|
|
6151
6209
|
return;
|
|
6152
6210
|
}
|
|
6153
6211
|
params = answer && typeof answer === "object" ? { ...next, ...answer } : next;
|
|
@@ -6428,7 +6486,13 @@ var L = {
|
|
|
6428
6486
|
suppress: "\uAC19\uC740 \uAC12 \uBC18\uBCF5 \uC228\uAE30\uAE30",
|
|
6429
6487
|
visible: "\uD45C\uC2DC \uC870\uAC74 (\uC2DD)",
|
|
6430
6488
|
canGrow: "\uB0B4\uC6A9\uC5D0 \uB9DE\uCDB0 \uB298\uB9AC\uAE30",
|
|
6431
|
-
stretch: "\
|
|
6489
|
+
stretch: "\uB192\uC774 \uB9DE\uCD94\uAE30",
|
|
6490
|
+
stretchAuto: "\uAE30\uBCF8 (\uBC34\uB4DC \uBC14\uB2E5\uC5D0 \uB2FF\uC73C\uBA74 \uBC34\uB4DC \uB192\uC774)",
|
|
6491
|
+
stretchBand: "\uBC34\uB4DC \uB192\uC774",
|
|
6492
|
+
stretchRow: "\uAC19\uC740 \uC904\uC758 \uAC00\uC7A5 \uB192\uC740 \uCE78",
|
|
6493
|
+
stretchNone: "\uB9DE\uCD94\uC9C0 \uC54A\uC74C",
|
|
6494
|
+
shift: "\uC704 \uC694\uC18C\uAC00 \uB298\uBA74 \uD568\uAED8 \uB0B4\uB824\uAC00\uAE30",
|
|
6495
|
+
collapse: "\uC228\uC73C\uBA74 \uC790\uB9AC \uC811\uAE30 (\uC544\uB798 \uC694\uC18C\uAC00 \uC62C\uB77C\uC634)",
|
|
6432
6496
|
style: "\uC2A4\uD0C0\uC77C",
|
|
6433
6497
|
bold: "\uAD75\uAC8C",
|
|
6434
6498
|
italic: "\uAE30\uC6B8\uC784",
|
|
@@ -6542,7 +6606,13 @@ var L = {
|
|
|
6542
6606
|
suppress: "Hide repeated values",
|
|
6543
6607
|
visible: "Visible when (expression)",
|
|
6544
6608
|
canGrow: "Grow to fit content",
|
|
6545
|
-
stretch: "
|
|
6609
|
+
stretch: "Height",
|
|
6610
|
+
stretchAuto: "Default (band height when touching its bottom)",
|
|
6611
|
+
stretchBand: "Band height",
|
|
6612
|
+
stretchRow: "Tallest cell on the same row",
|
|
6613
|
+
stretchNone: "Own height",
|
|
6614
|
+
shift: "Move down when an element above grows",
|
|
6615
|
+
collapse: "Give the space back when hidden",
|
|
6546
6616
|
style: "Style",
|
|
6547
6617
|
bold: "Bold",
|
|
6548
6618
|
italic: "Italic",
|
|
@@ -6801,7 +6871,16 @@ function elementFields(els, t, def) {
|
|
|
6801
6871
|
out.push({ label: t.shrink, kind: "check", get: () => !!same((e) => !!e.style?.shrinkToFit), set: (v) => styleSet("shrinkToFit")(v) });
|
|
6802
6872
|
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
6873
|
}
|
|
6874
|
+
out.push({
|
|
6875
|
+
label: t.stretch,
|
|
6876
|
+
kind: "select",
|
|
6877
|
+
options: [{ value: "", label: t.stretchAuto }, { value: "band", label: t.stretchBand }, { value: "row", label: t.stretchRow }, { value: "none", label: t.stretchNone }],
|
|
6878
|
+
get: () => same((e) => e.stretch === void 0 ? "" : e.stretch === "row" ? "row" : e.stretch ? "band" : "none"),
|
|
6879
|
+
set: (v) => setAll((e) => v === "row" ? e.stretch = "row" : v === "band" ? e.stretch = true : v === "none" ? e.stretch = false : delete e.stretch)
|
|
6880
|
+
});
|
|
6881
|
+
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
6882
|
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) });
|
|
6883
|
+
out.push({ label: t.collapse, kind: "check", get: () => same((e) => !!e.collapse), set: (v) => setAll((e) => v ? e.collapse = true : delete e.collapse) });
|
|
6805
6884
|
return out;
|
|
6806
6885
|
}
|
|
6807
6886
|
function bandFields(band, label, t, group, isDetail) {
|
|
@@ -7967,7 +8046,7 @@ async function renderPdf(def, data = {}, opts = {}) {
|
|
|
7967
8046
|
return writePdf(layout.pages, { title: def.name, creator: "FreeReport", ...opts.pdf, fonts: { default: first, ...fonts } });
|
|
7968
8047
|
}
|
|
7969
8048
|
async function renderXlsx(def, data = {}, opts = {}) {
|
|
7970
|
-
if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params });
|
|
8049
|
+
if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params, locale: opts.locale });
|
|
7971
8050
|
const { layout } = await prepare2(def, data, opts);
|
|
7972
8051
|
return pagesToXlsx(layout, def);
|
|
7973
8052
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freereportjs/core",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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",
|