@freereportjs/core 0.1.0 → 0.9.0

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.d.ts CHANGED
@@ -616,6 +616,12 @@ interface AgentStatus {
616
616
  port: number;
617
617
  /** True when this origin's stored token is valid. */
618
618
  paired: boolean;
619
+ /** Newer agent version the agent has found (it installs itself when idle), or null. Always null for agents before 1.1.0. */
620
+ updateAvailable: string | null;
621
+ /** Newest version from the agent's last update check, or null when it has not checked (or is older than 1.1.0). */
622
+ latestVersion: string | null;
623
+ /** True when this agent is older than the minimum supported version: ask the user to update. */
624
+ outdated: boolean;
619
625
  }
620
626
  /** One installed printer (`GET /v1/printers`). */
621
627
  interface AgentPrinter {
@@ -695,6 +701,7 @@ declare const agent: {
695
701
  /**
696
702
  * Find a running agent on 127.0.0.1 (ports 18731–18735, 600 ms each, tried in parallel).
697
703
  * Resolves to its status, or null when it is not installed / not running.
704
+ * `status.updateAvailable` / `latestVersion` / `outdated` report the agent's self-update state.
698
705
  */
699
706
  detect(): Promise<AgentStatus | null>;
700
707
  /**
@@ -788,6 +795,21 @@ interface ViewerOptions extends ReportOptions {
788
795
  /** Show the parameter panel open at start (default: when the report has parameters without defaults). */
789
796
  paramsOpen?: boolean;
790
797
  fileName?: string;
798
+ /** After every successful run (start, 조회/Run, setData, setParams, setDefinition): page and row counts. */
799
+ onReady?: (e: ViewerReadyEvent) => void;
800
+ /** When the user submits the parameter panel, before the data is fetched. Return false to stay (e.g. failed validation), or return changed params. */
801
+ onParamsSubmit?: (params: Record<string, unknown>) => boolean | void | Record<string, unknown> | Promise<boolean | void | Record<string, unknown>>;
802
+ /** The page in view changed (1-based). */
803
+ onPageChange?: (page: number, total: number) => void;
804
+ /** Loading data, laying out, printing or saving failed. The viewer still shows its own message. */
805
+ onError?: (error: Error, during: 'data' | 'layout' | 'output') => void;
806
+ }
807
+ interface ViewerReadyEvent {
808
+ pages: number;
809
+ rows: number;
810
+ /** Layout time in milliseconds. */
811
+ ms: number;
812
+ params: Record<string, unknown>;
791
813
  }
792
814
  interface Viewer {
793
815
  readonly report: Report;
@@ -1123,7 +1145,7 @@ declare function verifyLicense(key: string, host: string, opts?: {
1123
1145
  declare function setLicenseKey(key: string): void;
1124
1146
 
1125
1147
  /** FreeReport version (kept in sync with releases.json by scripts/release.mjs). */
1126
- declare const version = "0.1.0";
1148
+ declare const version = "0.9.0";
1127
1149
 
1128
1150
  type Lang = 'ko' | 'en';
1129
1151
 
@@ -1185,4 +1207,4 @@ declare function renderXlsx(def: ReportDefinition, data?: ReportData, opts?: Ser
1185
1207
  mode?: 'form' | 'data';
1186
1208
  }): Promise<Uint8Array>;
1187
1209
 
1188
- export { AgentError, type AgentLogEntry, type AgentPrintOptions, type AgentPrintResult, type AgentPrinter, type AgentStatus, type Align, type Band, type BandKind, type BarcodeElement, type BorderInput, type BorderSide, type BoxItem, type ChartElement, type ChartSeries, type CrosstabElement, type Designer, type DesignerOptions, type ElementType, type Emboss, type Expr, FUNCTIONS, type FieldElement, type FontBytes, type FontEntry, type GroupDefinition, type ImageElement, type ImageItem, type ItemSource, type LayoutOptions, type LayoutResult, type LicenseInfo, type LineElement, type LineItem, type OutputEvent, type OutputKind, PAPER, type PageItem, type PageSetup, type PaperSize, type ParamDefinition, type PathCommand, type PathItem, type PdfFontSource, type PdfOptions, type PdfPermissions, type PdfSecurity, type PrintOptions, type RectElement, type Report, type ReportData, type ReportDefinition, type ReportElement, type ReportOptions, type ReportPage, type ResolvedBorder, type ResolvedFont, type ServerRenderOptions, type Style, type TableColumn, type TableElement, type TextElement, type TextItem, type TextLine, type TextMeasurer, type VAlign, type Viewer, type ViewerOptions, type Watermark, type XCell, type XCellStyle, type XSheet, agent, applyEmboss, blankReport, canonicalText, canvasMeasurer, checkExpression, compile, contentWidth, createDesigner, createReport, createViewer, dataToCsv, dataToXlsx, defaultMeasurer, embossImage, encodeCode128, encodeDataMatrix, encodeEan13, encodePdf417, encodeQr, ensureFonts, estimateMeasurer, evaluate, fingerprintPages, formatDate, formatNumber, formatValue, getFont, interpolate, koreanAmount, koreanNumber, layoutReport, loadFontBytes, loadFonts, pageGeometry, pageToSvg, pagesToHwpx, pagesToXlsx, paramValues, printPages, registerFont, renderPage, renderPdf, renderXlsx, resetMeasurer, rowsOf, saveFile, setLicenseKey, sha256Hex, ttfMeasurer, verifyLicense, version, wrapText, writePdf, writeXlsx };
1210
+ export { AgentError, type AgentLogEntry, type AgentPrintOptions, type AgentPrintResult, type AgentPrinter, type AgentStatus, type Align, type Band, type BandKind, type BarcodeElement, type BorderInput, type BorderSide, type BoxItem, type ChartElement, type ChartSeries, type CrosstabElement, type Designer, type DesignerOptions, type ElementType, type Emboss, type Expr, FUNCTIONS, type FieldElement, type FontBytes, type FontEntry, type GroupDefinition, type ImageElement, type ImageItem, type ItemSource, type LayoutOptions, type LayoutResult, type LicenseInfo, type LineElement, type LineItem, type OutputEvent, type OutputKind, PAPER, type PageItem, type PageSetup, type PaperSize, type ParamDefinition, type PathCommand, type PathItem, type PdfFontSource, type PdfOptions, type PdfPermissions, type PdfSecurity, type PrintOptions, type RectElement, type Report, type ReportData, type ReportDefinition, type ReportElement, type ReportOptions, type ReportPage, type ResolvedBorder, type ResolvedFont, type ServerRenderOptions, type Style, type TableColumn, type TableElement, type TextElement, type TextItem, type TextLine, type TextMeasurer, type VAlign, type Viewer, type ViewerOptions, type ViewerReadyEvent, type Watermark, type XCell, type XCellStyle, type XSheet, agent, applyEmboss, blankReport, canonicalText, canvasMeasurer, checkExpression, compile, contentWidth, createDesigner, createReport, createViewer, dataToCsv, dataToXlsx, defaultMeasurer, embossImage, encodeCode128, encodeDataMatrix, encodeEan13, encodePdf417, encodeQr, ensureFonts, estimateMeasurer, evaluate, fingerprintPages, formatDate, formatNumber, formatValue, getFont, interpolate, koreanAmount, koreanNumber, layoutReport, loadFontBytes, loadFonts, pageGeometry, pageToSvg, pagesToHwpx, pagesToXlsx, paramValues, printPages, registerFont, renderPage, renderPdf, renderXlsx, resetMeasurer, rowsOf, saveFile, setLicenseKey, sha256Hex, ttfMeasurer, verifyLicense, version, wrapText, writePdf, writeXlsx };
package/dist/index.js CHANGED
@@ -187,8 +187,8 @@ function pdfFonts(pages) {
187
187
  }
188
188
 
189
189
  // src/version.ts
190
- var version = "0.1.0";
191
- var releaseDate = "2026-10-15";
190
+ var version = "0.9.0";
191
+ var releaseDate = "2026-10-16";
192
192
 
193
193
  // src/license.ts
194
194
  var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "MiJXJR8TZJ_-0VKk0wIQ-5mzRsyQ6ruGsbcRRdw9jrw", y: "MTdklYSzCM2pganffz7PPMfxSclQkBLqU_9se_VDznM" };
@@ -5365,6 +5365,14 @@ html, body { margin: 0; padding: 0; background: #fff; }
5365
5365
  }
5366
5366
 
5367
5367
  // src/agent.ts
5368
+ function normalizeStatus(s) {
5369
+ return {
5370
+ ...s,
5371
+ updateAvailable: typeof s.updateAvailable === "string" ? s.updateAvailable : null,
5372
+ latestVersion: typeof s.latestVersion === "string" ? s.latestVersion : null,
5373
+ outdated: s.outdated === true
5374
+ };
5375
+ }
5368
5376
  var AgentError = class extends Error {
5369
5377
  constructor(message, code, status) {
5370
5378
  super(message);
@@ -5412,7 +5420,7 @@ async function fetchJson(url, init = {}) {
5412
5420
  async function probe(port) {
5413
5421
  const s = await fetchJson(`http://127.0.0.1:${port}/v1/status`, { timeout: DETECT_TIMEOUT });
5414
5422
  if (s?.name !== AGENT_NAME) throw new Error("not the agent");
5415
- return s;
5423
+ return normalizeStatus(s);
5416
5424
  }
5417
5425
  async function requireBase() {
5418
5426
  if (base) return base;
@@ -5442,12 +5450,13 @@ var agent = {
5442
5450
  /**
5443
5451
  * Find a running agent on 127.0.0.1 (ports 18731–18735, 600 ms each, tried in parallel).
5444
5452
  * Resolves to its status, or null when it is not installed / not running.
5453
+ * `status.updateAvailable` / `latestVersion` / `outdated` report the agent's self-update state.
5445
5454
  */
5446
5455
  async detect() {
5447
5456
  if (typeof fetch === "undefined") return null;
5448
5457
  if (base) {
5449
5458
  try {
5450
- return await fetchJson(`${base}/v1/status`, { timeout: DETECT_TIMEOUT });
5459
+ return normalizeStatus(await fetchJson(`${base}/v1/status`, { timeout: DETECT_TIMEOUT }));
5451
5460
  } catch {
5452
5461
  base = null;
5453
5462
  }
@@ -5850,6 +5859,27 @@ function createViewer(el, options) {
5850
5859
  const pageH = result.height * MM_PX * scale + PAGE_GAP;
5851
5860
  current = Math.min(result.pages.length - 1, Math.max(0, Math.round(body.scrollTop / pageH)));
5852
5861
  if (pageInput && document.activeElement !== pageInput) pageInput.value = String(current + 1);
5862
+ notifyPage();
5863
+ };
5864
+ let reported = -1;
5865
+ const notifyPage = () => {
5866
+ if (!result?.pages.length || current === reported) return;
5867
+ reported = current;
5868
+ try {
5869
+ options.onPageChange?.(current + 1, result.pages.length);
5870
+ } catch (e) {
5871
+ console.error(e);
5872
+ }
5873
+ };
5874
+ const fail = (e, during) => {
5875
+ const err = e instanceof Error ? e : new Error(String(e));
5876
+ console.error(err);
5877
+ try {
5878
+ options.onError?.(err, during);
5879
+ } catch (x) {
5880
+ console.error(x);
5881
+ }
5882
+ return err;
5853
5883
  };
5854
5884
  const goTo = (n2) => {
5855
5885
  if (!result) return;
@@ -5858,6 +5888,7 @@ function createViewer(el, options) {
5858
5888
  if (holder) body.scrollTop = holder.offsetTop - pagesEl.offsetTop;
5859
5889
  current = i;
5860
5890
  if (pageInput) pageInput.value = String(i + 1);
5891
+ notifyPage();
5861
5892
  };
5862
5893
  const setZoom = (z) => {
5863
5894
  const ratio = result ? body.scrollTop / Math.max(1, body.scrollHeight) : 0;
@@ -5869,20 +5900,28 @@ function createViewer(el, options) {
5869
5900
  const run = async () => {
5870
5901
  const seq2 = ++runSeq;
5871
5902
  busy(t.loading);
5903
+ let during = "data";
5872
5904
  try {
5873
5905
  if (options.fetchData) {
5874
5906
  const fetched = await options.fetchData(params);
5875
5907
  if (seq2 !== runSeq) return;
5876
5908
  report.setData(fetched);
5877
5909
  }
5910
+ during = "layout";
5878
5911
  report.setParams(params);
5879
5912
  await ensureFonts(report.definition);
5880
5913
  if (destroyed) return;
5881
5914
  result = report.layout();
5915
+ reported = -1;
5882
5916
  build();
5917
+ try {
5918
+ options.onReady?.({ pages: result.pages.length, rows: result.rowCount, ms: result.ms, params: { ...params } });
5919
+ } catch (e) {
5920
+ console.error(e);
5921
+ }
5883
5922
  } catch (e) {
5884
- console.error(e);
5885
- pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(String(e.message ?? e))}</p>`;
5923
+ const err = fail(e, during);
5924
+ pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(err.message)}</p>`;
5886
5925
  } finally {
5887
5926
  busy(null);
5888
5927
  }
@@ -5892,8 +5931,7 @@ function createViewer(el, options) {
5892
5931
  try {
5893
5932
  await report.download(kind, options.fileName);
5894
5933
  } catch (e) {
5895
- console.error(e);
5896
- alertText(`${t.error}: ${e.message}`);
5934
+ alertText(`${t.error}: ${fail(e, "output").message}`);
5897
5935
  } finally {
5898
5936
  busy(null);
5899
5937
  }
@@ -5935,7 +5973,7 @@ function createViewer(el, options) {
5935
5973
  setZoom(Math.round((scale - 0.1) * 10) / 10);
5936
5974
  break;
5937
5975
  case "print":
5938
- void report.print().catch((err) => alertText(`${t.error}: ${err.message}`));
5976
+ void report.print().catch((err) => alertText(`${t.error}: ${fail(err, "output").message}`));
5939
5977
  break;
5940
5978
  case "pdf":
5941
5979
  void exportAs("pdf");
@@ -6010,7 +6048,7 @@ function createViewer(el, options) {
6010
6048
  e.preventDefault();
6011
6049
  setZoom(Math.round(scale * (e.deltaY < 0 ? 1.1 : 1 / 1.1) * 100) / 100);
6012
6050
  }, { passive: false });
6013
- form?.addEventListener("submit", (e) => {
6051
+ form?.addEventListener("submit", async (e) => {
6014
6052
  e.preventDefault();
6015
6053
  const next = { ...params };
6016
6054
  form.querySelectorAll("input, select").forEach((inp) => {
@@ -6018,7 +6056,15 @@ function createViewer(el, options) {
6018
6056
  const v = inp.value;
6019
6057
  next[inp.name] = type === "number" ? v === "" ? null : Number(v) : type === "boolean" ? v === "true" : v;
6020
6058
  });
6021
- params = next;
6059
+ let answer;
6060
+ try {
6061
+ answer = await options.onParamsSubmit?.({ ...next });
6062
+ } catch (x) {
6063
+ fail(x, "data");
6064
+ return;
6065
+ }
6066
+ if (answer === false) return;
6067
+ params = answer && typeof answer === "object" ? { ...next, ...answer } : next;
6022
6068
  void run();
6023
6069
  });
6024
6070
  const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
package/dist/styles.css CHANGED
@@ -1,168 +1,168 @@
1
- /* FreeReport viewer and designer. Override the --fr-* variables to theme. */
2
- .fr-viewer, .fr-designer {
3
- --fr-bg: #e9ecf1;
4
- --fr-panel: #ffffff;
5
- --fr-ink: #0e1726;
6
- --fr-ink-2: #4a5568;
7
- --fr-rule: #d5dbe3;
8
- --fr-accent: #2340f5;
9
- --fr-accent-ink: #ffffff;
10
- --fr-hover: #f1f3f7;
11
- --fr-radius: 6px;
12
- --fr-font: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", "Segoe UI", sans-serif;
13
- display: flex; flex-direction: column; min-height: 0; height: 100%;
14
- font: 13px/1.4 var(--fr-font); color: var(--fr-ink); background: var(--fr-bg);
15
- border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); overflow: hidden; box-sizing: border-box;
16
- }
17
- .fr-viewer *, .fr-designer * { box-sizing: border-box; }
18
-
19
- .fr-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; padding: 6px 8px; background: var(--fr-panel); border-bottom: 1px solid var(--fr-rule); }
20
- .fr-btn { display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 9px; border: 1px solid transparent; border-radius: var(--fr-radius); background: none; color: var(--fr-ink); font: inherit; font-weight: 500; cursor: pointer; white-space: nowrap; }
21
- .fr-btn:hover { background: var(--fr-hover); }
22
- .fr-btn:focus-visible, .fr-viewer select:focus-visible, .fr-viewer input:focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 1px; }
23
- .fr-btn:disabled { opacity: .45; cursor: default; }
24
- .fr-btn.fr-icon { width: 30px; padding: 0; justify-content: center; }
25
- .fr-btn.fr-primary { background: var(--fr-accent); color: var(--fr-accent-ink); }
26
- .fr-btn.fr-primary:hover { filter: brightness(1.08); }
27
- .fr-btn[aria-expanded="true"], .fr-btn[aria-pressed="true"] { background: var(--fr-hover); border-color: var(--fr-rule); }
28
- .fr-btn svg { flex: none; }
29
- .fr-sep { width: 1px; height: 20px; margin: 0 6px; background: var(--fr-rule); }
30
- .fr-spacer { flex: 1; }
31
- .fr-pageno { display: inline-flex; align-items: center; gap: 4px; margin: 0 4px; color: var(--fr-ink-2); font-variant-numeric: tabular-nums; }
32
- .fr-pageno input { width: 44px; height: 28px; text-align: center; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; color: var(--fr-ink); }
33
- .fr-zoom { height: 30px; padding: 0 6px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: var(--fr-panel); font: inherit; color: var(--fr-ink); }
34
- .fr-menu { position: relative; }
35
- .fr-menu-list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; min-width: 180px; padding: 4px; background: var(--fr-panel); border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); box-shadow: 0 8px 24px rgba(14, 23, 38, .14); display: flex; flex-direction: column; }
36
- .fr-menu-list[hidden] { display: none; }
37
- .fr-menu-list button { text-align: left; padding: 8px 10px; border: 0; background: none; border-radius: 4px; font: inherit; color: var(--fr-ink); cursor: pointer; }
38
- .fr-menu-list button:hover, .fr-menu-list button:focus-visible { background: var(--fr-hover); outline: none; }
39
-
40
- .fr-params { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; padding: 10px 12px; background: var(--fr-panel); border-bottom: 1px solid var(--fr-rule); }
41
- .fr-params[hidden] { display: none; }
42
- .fr-param { display: flex; flex-direction: column; gap: 3px; }
43
- .fr-param label { font-size: 12px; color: var(--fr-ink-2); font-weight: 600; }
44
- .fr-param label b { color: #c8102e; }
45
- .fr-param input, .fr-param select { height: 30px; min-width: 140px; padding: 0 8px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; background: #fff; color: var(--fr-ink); }
46
-
47
- .fr-body { position: relative; flex: 1; min-height: 0; overflow: auto; outline: none; }
48
- .fr-pages { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 16px 24px 32px; min-width: min-content; }
49
- .fr-page { width: var(--fr-page-w, 794px); height: var(--fr-page-h, 1123px); flex: none; background: #fff; box-shadow: 0 1px 3px rgba(14, 23, 38, .18), 0 6px 18px -8px rgba(14, 23, 38, .25); }
50
- .fr-page-svg { display: block; width: 100%; height: 100%; }
51
- .fr-page { position: relative; }
52
- .fr-hit { position: absolute; background: rgba(242, 169, 0, .38); outline: 2px solid #f2a900; border-radius: 2px; pointer-events: none; }
53
- .fr-search { display: inline-flex; align-items: center; gap: 4px; height: 30px; padding: 0 8px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); color: var(--fr-ink-2); background: var(--fr-panel); }
54
- .fr-search input { width: 120px; border: 0; outline: none; font: inherit; color: var(--fr-ink); background: none; }
55
- .fr-search:focus-within { border-color: var(--fr-accent); }
56
- .fr-search-count { font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }
57
- .fr-empty { margin: 48px auto; color: var(--fr-ink-2); }
58
- .fr-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 10px; background: rgba(233, 236, 241, .72); font-weight: 600; color: var(--fr-ink-2); z-index: 5; }
59
- .fr-overlay[hidden] { display: none; }
60
- .fr-spinner { width: 18px; height: 18px; border: 2px solid var(--fr-rule); border-top-color: var(--fr-accent); border-radius: 50%; animation: fr-spin .8s linear infinite; }
61
- @keyframes fr-spin { to { transform: rotate(360deg); } }
62
- @media (prefers-reduced-motion: reduce) { .fr-spinner { animation-duration: 2.4s; } }
63
- .fr-status { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 4px 10px; background: var(--fr-panel); border-top: 1px solid var(--fr-rule); color: var(--fr-ink-2); font-size: 12px; font-variant-numeric: tabular-nums; min-height: 26px; }
64
- .fr-badge { color: var(--fr-ink-2); text-decoration: none; border: 1px solid var(--fr-rule); border-radius: 999px; padding: 0 8px; }
65
- .fr-badge[hidden] { display: none; }
66
-
67
- @media (max-width: 640px) {
68
- .fr-btn span { display: none; }
69
- .fr-btn { padding: 0 7px; }
70
- .fr-pages { padding: 10px 8px 24px; }
71
- }
72
- @media print {
73
- .fr-viewer .fr-toolbar, .fr-viewer .fr-status, .fr-viewer .fr-params { display: none; }
74
- }
75
-
76
- /* ── Designer ─────────────────────────────────── */
77
- .fr-designer { outline: none; }
78
- .fr-designer .fr-toolbar { gap: 1px; }
79
- .fr-designer .fr-group { display: inline-flex; align-items: center; gap: 1px; flex-wrap: wrap; }
80
- .fr-designer.is-preview .fr-design-only, .fr-designer.is-json .fr-design-only { display: none; }
81
- .fr-tabs { display: inline-flex; padding: 2px; background: var(--fr-bg); border-radius: 7px; }
82
- .fr-tab { height: 26px; padding: 0 12px; border: 0; border-radius: 5px; background: none; font: inherit; font-weight: 600; color: var(--fr-ink-2); cursor: pointer; }
83
- .fr-tab[aria-selected="true"] { background: var(--fr-panel); color: var(--fr-ink); box-shadow: 0 1px 2px rgba(14, 23, 38, .12); }
84
- .fr-zoom-label { min-width: 42px; text-align: center; font-variant-numeric: tabular-nums; color: var(--fr-ink-2); }
85
- .fr-dz { flex: 1; min-height: 0; display: grid; grid-template-columns: 200px minmax(0, 1fr) 270px; }
86
- .fr-designer.is-preview .fr-dz, .fr-designer.is-json .fr-dz { grid-template-columns: minmax(0, 1fr); }
87
- .fr-designer.is-preview .fr-dz-left, .fr-designer.is-preview .fr-dz-right, .fr-designer.is-json .fr-dz-left, .fr-designer.is-json .fr-dz-right { display: none; }
88
- .fr-dz-left, .fr-dz-right { background: var(--fr-panel); overflow: auto; min-height: 0; }
89
- .fr-dz-left { border-right: 1px solid var(--fr-rule); padding: 8px; }
90
- .fr-dz-right { border-left: 1px solid var(--fr-rule); }
91
- .fr-dz-left h3 { margin: 10px 4px 6px; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--fr-ink-2); }
92
- .fr-dz-left h3:first-child { margin-top: 2px; }
93
- .fr-dz-tools { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
94
- .fr-dz-tool { display: flex; align-items: center; gap: 6px; padding: 6px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: var(--fr-panel); font: inherit; font-size: 12px; color: var(--fr-ink); cursor: grab; text-align: left; }
95
- .fr-dz-tool:hover, .fr-dz-field:hover { border-color: var(--fr-accent); color: var(--fr-accent); }
96
- .fr-dz-fields { display: flex; flex-wrap: wrap; gap: 4px; }
97
- .fr-dz-field { padding: 3px 8px; border: 1px solid var(--fr-rule); border-radius: 999px; background: #f7f9fc; font: 12px/1.4 ui-monospace, Consolas, monospace; color: var(--fr-ink); cursor: grab; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
98
- .fr-dz-empty { margin: 0 4px; color: var(--fr-ink-2); font-size: 12px; }
99
- .fr-dz-bandopt { display: flex; align-items: center; gap: 6px; padding: 2px 4px; font-size: 12.5px; cursor: pointer; }
100
- .fr-dz-groupopt { display: flex; align-items: center; justify-content: space-between; gap: 4px; padding: 2px 4px; font-size: 12px; }
101
- .fr-dz-groupopt code { font-size: 11.5px; }
102
- .fr-dz-addgroup { display: flex; gap: 4px; margin-top: 6px; }
103
- .fr-dz-addgroup select { flex: 1; min-width: 0; height: 30px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; font-size: 12px; }
104
- .fr-dz-addgroup .fr-btn span { font-size: 12px; }
105
- .fr-dz-center { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
106
- .fr-dz-canvas { flex: 1; overflow: auto; padding: 24px 32px 64px; outline: none; }
107
- .fr-dz-canvas[hidden], .fr-dz-preview[hidden], .fr-dz-json[hidden] { display: none; }
108
- .fr-dz-page { margin: 0 auto; background: #fff; box-shadow: 0 1px 3px rgba(14, 23, 38, .16), 0 8px 24px -12px rgba(14, 23, 38, .3); user-select: none; }
109
- .fr-dz-band { position: relative; }
110
- .fr-dz-band-head { display: flex; justify-content: space-between; align-items: center; height: 20px; padding: 0 8px; background: #eef1f6; border-top: 1px solid var(--fr-rule); border-bottom: 1px solid var(--fr-rule); font-size: 11px; font-weight: 600; color: var(--fr-ink-2); cursor: pointer; }
111
- .fr-dz-band-head small { font-weight: 500; font-variant-numeric: tabular-nums; }
112
- .fr-dz-band.is-selected .fr-dz-band-head { background: #dfe6ff; color: var(--fr-accent); }
113
- .fr-dz-band-area { position: relative; background-image: radial-gradient(circle, #d3d9e3 .8px, transparent .9px); overflow: visible; }
114
- .fr-dz-band-resize { height: 5px; margin-top: -2px; position: relative; z-index: 3; cursor: ns-resize; }
115
- .fr-dz-band-resize:hover { background: var(--fr-accent); opacity: .5; }
116
- .fr-dz-hint { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: #a6b0c0; font-size: 11px; pointer-events: none; white-space: nowrap; }
117
- .fr-dz-el { position: absolute; cursor: move; outline: 1px dashed rgba(35, 64, 245, .18); }
118
- .fr-dz-el:hover { outline-color: rgba(35, 64, 245, .55); }
119
- .fr-dz-el.is-selected { outline: 1.5px solid var(--fr-accent); z-index: 2; }
120
- .fr-dz-el.is-line { outline-offset: 2px; }
121
- .fr-dz-el > * { pointer-events: none; }
122
- .fr-dz-el > .fr-dz-h, .fr-dz-el > .fr-dz-inline { pointer-events: auto; }
123
- .fr-dz-text { position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden; white-space: pre-wrap; word-break: break-all; }
124
- .fr-dz-text small { margin-left: 4px; font-size: 9px; color: #7a8599; font-weight: 400; }
125
- .fr-dz-expr { color: #1a2fc0; }
126
- .fr-dz-rect { position: absolute; inset: 0; }
127
- .fr-dz-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
128
- .fr-dz-el img { width: 100%; height: 100%; display: block; }
129
- .fr-dz-ph { position: absolute; inset: 0; display: grid; place-items: center; background: repeating-linear-gradient(45deg, #f4f6fa, #f4f6fa 4px, #eceff5 4px, #eceff5 8px); color: #8a94a6; }
130
- .fr-dz-h { position: absolute; width: 8px; height: 8px; background: #fff; border: 1.5px solid var(--fr-accent); border-radius: 2px; z-index: 3; }
131
- .fr-dz-h-nw { left: -4px; top: -4px; cursor: nwse-resize; }
132
- .fr-dz-h-n { left: calc(50% - 4px); top: -4px; cursor: ns-resize; }
133
- .fr-dz-h-ne { right: -4px; top: -4px; cursor: nesw-resize; }
134
- .fr-dz-h-e { right: -4px; top: calc(50% - 4px); cursor: ew-resize; }
135
- .fr-dz-h-se { right: -4px; bottom: -4px; cursor: nwse-resize; }
136
- .fr-dz-h-s { left: calc(50% - 4px); bottom: -4px; cursor: ns-resize; }
137
- .fr-dz-h-sw { left: -4px; bottom: -4px; cursor: nesw-resize; }
138
- .fr-dz-h-w { left: -4px; top: calc(50% - 4px); cursor: ew-resize; }
139
- .fr-dz-marquee { position: absolute; border: 1px solid var(--fr-accent); background: rgba(35, 64, 245, .08); pointer-events: none; z-index: 4; }
140
- .fr-dz-inline { position: absolute; inset: 0; min-height: 24px; z-index: 5; font: 13px/1.4 var(--fr-font); padding: 2px 4px; border: 2px solid var(--fr-accent); resize: none; }
141
- .fr-dz-preview { flex: 1; min-height: 0; }
142
- .fr-dz-preview .fr-viewer { border: 0; border-radius: 0; }
143
- .fr-dz-json { flex: 1; min-height: 0; display: flex; flex-direction: column; }
144
- .fr-dz-json textarea { flex: 1; border: 0; padding: 14px 16px; font: 12.5px/1.55 ui-monospace, "Cascadia Code", Consolas, monospace; resize: none; outline: none; background: #fbfcfe; color: var(--fr-ink); }
145
- .fr-dz-json-bar { display: flex; justify-content: flex-end; align-items: center; gap: 12px; padding: 8px 12px; border-top: 1px solid var(--fr-rule); background: var(--fr-panel); }
146
- .fr-dz-json-err { color: #c8102e; font-size: 12px; }
147
- .fr-dz-props { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 4px 12px 24px; }
148
- .fr-dz-sec { grid-column: 1 / -1; margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--fr-rule); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--fr-ink-2); }
149
- .fr-dz-sec:first-child { border-top: 0; margin-top: 4px; padding-top: 4px; }
150
- .fr-dz-prop { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
151
- .fr-dz-prop.is-wide { grid-column: 1 / -1; }
152
- .fr-dz-prop.is-check { flex-direction: row; align-items: center; gap: 6px; grid-column: 1 / -1; font-size: 12.5px; cursor: pointer; }
153
- .fr-dz-plabel { font-size: 11.5px; color: var(--fr-ink-2); font-weight: 600; }
154
- .fr-dz-prop input[type=text], .fr-dz-prop input[type=number], .fr-dz-prop select, .fr-dz-prop textarea { width: 100%; height: 28px; padding: 0 7px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; font-size: 12.5px; color: var(--fr-ink); background: #fff; }
155
- .fr-dz-prop textarea { height: auto; padding: 5px 7px; font-family: ui-monospace, Consolas, monospace; resize: vertical; }
156
- .fr-dz-prop input.is-expr { font-family: ui-monospace, Consolas, monospace; }
157
- .fr-dz-prop :focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 0; }
158
- .fr-dz-err { color: #c8102e; font-size: 11px; }
159
- .fr-dz-err:empty { display: none; }
160
- .fr-dz-color { display: flex; gap: 4px; }
161
- .fr-dz-color input[type=color] { width: 30px; height: 28px; padding: 1px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: #fff; flex: none; }
162
- .fr-dz-toggles { display: flex; gap: 3px; flex-wrap: wrap; }
163
- .fr-dz-toggle { width: 30px; height: 28px; padding: 0; justify-content: center; border: 1px solid var(--fr-rule); font-weight: 700; }
164
- .fr-dz-toggle[aria-pressed="true"] { background: #dfe6ff; border-color: var(--fr-accent); color: var(--fr-accent); }
165
- @media (max-width: 960px) {
166
- .fr-dz { grid-template-columns: 150px minmax(0, 1fr); }
167
- .fr-dz-right { display: none; }
168
- }
1
+ /* FreeReport viewer and designer. Override the --fr-* variables to theme. */
2
+ .fr-viewer, .fr-designer {
3
+ --fr-bg: #e9ecf1;
4
+ --fr-panel: #ffffff;
5
+ --fr-ink: #0e1726;
6
+ --fr-ink-2: #4a5568;
7
+ --fr-rule: #d5dbe3;
8
+ --fr-accent: #2340f5;
9
+ --fr-accent-ink: #ffffff;
10
+ --fr-hover: #f1f3f7;
11
+ --fr-radius: 6px;
12
+ --fr-font: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", "Segoe UI", sans-serif;
13
+ display: flex; flex-direction: column; min-height: 0; height: 100%;
14
+ font: 13px/1.4 var(--fr-font); color: var(--fr-ink); background: var(--fr-bg);
15
+ border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); overflow: hidden; box-sizing: border-box;
16
+ }
17
+ .fr-viewer *, .fr-designer * { box-sizing: border-box; }
18
+
19
+ .fr-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; padding: 6px 8px; background: var(--fr-panel); border-bottom: 1px solid var(--fr-rule); }
20
+ .fr-btn { display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 9px; border: 1px solid transparent; border-radius: var(--fr-radius); background: none; color: var(--fr-ink); font: inherit; font-weight: 500; cursor: pointer; white-space: nowrap; }
21
+ .fr-btn:hover { background: var(--fr-hover); }
22
+ .fr-btn:focus-visible, .fr-viewer select:focus-visible, .fr-viewer input:focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 1px; }
23
+ .fr-btn:disabled { opacity: .45; cursor: default; }
24
+ .fr-btn.fr-icon { width: 30px; padding: 0; justify-content: center; }
25
+ .fr-btn.fr-primary { background: var(--fr-accent); color: var(--fr-accent-ink); }
26
+ .fr-btn.fr-primary:hover { filter: brightness(1.08); }
27
+ .fr-btn[aria-expanded="true"], .fr-btn[aria-pressed="true"] { background: var(--fr-hover); border-color: var(--fr-rule); }
28
+ .fr-btn svg { flex: none; }
29
+ .fr-sep { width: 1px; height: 20px; margin: 0 6px; background: var(--fr-rule); }
30
+ .fr-spacer { flex: 1; }
31
+ .fr-pageno { display: inline-flex; align-items: center; gap: 4px; margin: 0 4px; color: var(--fr-ink-2); font-variant-numeric: tabular-nums; }
32
+ .fr-pageno input { width: 44px; height: 28px; text-align: center; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; color: var(--fr-ink); }
33
+ .fr-zoom { height: 30px; padding: 0 6px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: var(--fr-panel); font: inherit; color: var(--fr-ink); }
34
+ .fr-menu { position: relative; }
35
+ .fr-menu-list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; min-width: 180px; padding: 4px; background: var(--fr-panel); border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); box-shadow: 0 8px 24px rgba(14, 23, 38, .14); display: flex; flex-direction: column; }
36
+ .fr-menu-list[hidden] { display: none; }
37
+ .fr-menu-list button { text-align: left; padding: 8px 10px; border: 0; background: none; border-radius: 4px; font: inherit; color: var(--fr-ink); cursor: pointer; }
38
+ .fr-menu-list button:hover, .fr-menu-list button:focus-visible { background: var(--fr-hover); outline: none; }
39
+
40
+ .fr-params { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; padding: 10px 12px; background: var(--fr-panel); border-bottom: 1px solid var(--fr-rule); }
41
+ .fr-params[hidden] { display: none; }
42
+ .fr-param { display: flex; flex-direction: column; gap: 3px; }
43
+ .fr-param label { font-size: 12px; color: var(--fr-ink-2); font-weight: 600; }
44
+ .fr-param label b { color: #c8102e; }
45
+ .fr-param input, .fr-param select { height: 30px; min-width: 140px; padding: 0 8px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; background: #fff; color: var(--fr-ink); }
46
+
47
+ .fr-body { position: relative; flex: 1; min-height: 0; overflow: auto; outline: none; }
48
+ .fr-pages { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 16px 24px 32px; min-width: min-content; }
49
+ .fr-page { width: var(--fr-page-w, 794px); height: var(--fr-page-h, 1123px); flex: none; background: #fff; box-shadow: 0 1px 3px rgba(14, 23, 38, .18), 0 6px 18px -8px rgba(14, 23, 38, .25); }
50
+ .fr-page-svg { display: block; width: 100%; height: 100%; }
51
+ .fr-page { position: relative; }
52
+ .fr-hit { position: absolute; background: rgba(242, 169, 0, .38); outline: 2px solid #f2a900; border-radius: 2px; pointer-events: none; }
53
+ .fr-search { display: inline-flex; align-items: center; gap: 4px; height: 30px; padding: 0 8px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); color: var(--fr-ink-2); background: var(--fr-panel); }
54
+ .fr-search input { width: 120px; border: 0; outline: none; font: inherit; color: var(--fr-ink); background: none; }
55
+ .fr-search:focus-within { border-color: var(--fr-accent); }
56
+ .fr-search-count { font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }
57
+ .fr-empty { margin: 48px auto; color: var(--fr-ink-2); }
58
+ .fr-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 10px; background: rgba(233, 236, 241, .72); font-weight: 600; color: var(--fr-ink-2); z-index: 5; }
59
+ .fr-overlay[hidden] { display: none; }
60
+ .fr-spinner { width: 18px; height: 18px; border: 2px solid var(--fr-rule); border-top-color: var(--fr-accent); border-radius: 50%; animation: fr-spin .8s linear infinite; }
61
+ @keyframes fr-spin { to { transform: rotate(360deg); } }
62
+ @media (prefers-reduced-motion: reduce) { .fr-spinner { animation-duration: 2.4s; } }
63
+ .fr-status { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 4px 10px; background: var(--fr-panel); border-top: 1px solid var(--fr-rule); color: var(--fr-ink-2); font-size: 12px; font-variant-numeric: tabular-nums; min-height: 26px; }
64
+ .fr-badge { color: var(--fr-ink-2); text-decoration: none; border: 1px solid var(--fr-rule); border-radius: 999px; padding: 0 8px; }
65
+ .fr-badge[hidden] { display: none; }
66
+
67
+ @media (max-width: 640px) {
68
+ .fr-btn span { display: none; }
69
+ .fr-btn { padding: 0 7px; }
70
+ .fr-pages { padding: 10px 8px 24px; }
71
+ }
72
+ @media print {
73
+ .fr-viewer .fr-toolbar, .fr-viewer .fr-status, .fr-viewer .fr-params { display: none; }
74
+ }
75
+
76
+ /* ── Designer ─────────────────────────────────── */
77
+ .fr-designer { outline: none; }
78
+ .fr-designer .fr-toolbar { gap: 1px; }
79
+ .fr-designer .fr-group { display: inline-flex; align-items: center; gap: 1px; flex-wrap: wrap; }
80
+ .fr-designer.is-preview .fr-design-only, .fr-designer.is-json .fr-design-only { display: none; }
81
+ .fr-tabs { display: inline-flex; padding: 2px; background: var(--fr-bg); border-radius: 7px; }
82
+ .fr-tab { height: 26px; padding: 0 12px; border: 0; border-radius: 5px; background: none; font: inherit; font-weight: 600; color: var(--fr-ink-2); cursor: pointer; }
83
+ .fr-tab[aria-selected="true"] { background: var(--fr-panel); color: var(--fr-ink); box-shadow: 0 1px 2px rgba(14, 23, 38, .12); }
84
+ .fr-zoom-label { min-width: 42px; text-align: center; font-variant-numeric: tabular-nums; color: var(--fr-ink-2); }
85
+ .fr-dz { flex: 1; min-height: 0; display: grid; grid-template-columns: 200px minmax(0, 1fr) 270px; }
86
+ .fr-designer.is-preview .fr-dz, .fr-designer.is-json .fr-dz { grid-template-columns: minmax(0, 1fr); }
87
+ .fr-designer.is-preview .fr-dz-left, .fr-designer.is-preview .fr-dz-right, .fr-designer.is-json .fr-dz-left, .fr-designer.is-json .fr-dz-right { display: none; }
88
+ .fr-dz-left, .fr-dz-right { background: var(--fr-panel); overflow: auto; min-height: 0; }
89
+ .fr-dz-left { border-right: 1px solid var(--fr-rule); padding: 8px; }
90
+ .fr-dz-right { border-left: 1px solid var(--fr-rule); }
91
+ .fr-dz-left h3 { margin: 10px 4px 6px; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--fr-ink-2); }
92
+ .fr-dz-left h3:first-child { margin-top: 2px; }
93
+ .fr-dz-tools { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
94
+ .fr-dz-tool { display: flex; align-items: center; gap: 6px; padding: 6px; word-break: keep-all; overflow-wrap: anywhere; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: var(--fr-panel); font: inherit; font-size: 12px; color: var(--fr-ink); cursor: grab; text-align: left; }
95
+ .fr-dz-tool:hover, .fr-dz-field:hover { border-color: var(--fr-accent); color: var(--fr-accent); }
96
+ .fr-dz-fields { display: flex; flex-wrap: wrap; gap: 4px; }
97
+ .fr-dz-field { padding: 3px 8px; border: 1px solid var(--fr-rule); border-radius: 999px; background: #f7f9fc; font: 12px/1.4 ui-monospace, Consolas, monospace; color: var(--fr-ink); cursor: grab; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
98
+ .fr-dz-empty { margin: 0 4px; color: var(--fr-ink-2); font-size: 12px; }
99
+ .fr-dz-bandopt { display: flex; align-items: center; gap: 6px; padding: 2px 4px; font-size: 12.5px; cursor: pointer; }
100
+ .fr-dz-groupopt { display: flex; align-items: center; justify-content: space-between; gap: 4px; padding: 2px 4px; font-size: 12px; }
101
+ .fr-dz-groupopt code { font-size: 11.5px; }
102
+ .fr-dz-addgroup { display: flex; gap: 4px; margin-top: 6px; }
103
+ .fr-dz-addgroup select { flex: 1; min-width: 0; height: 30px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; font-size: 12px; }
104
+ .fr-dz-addgroup .fr-btn span { font-size: 12px; }
105
+ .fr-dz-center { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
106
+ .fr-dz-canvas { flex: 1; overflow: auto; padding: 24px 32px 64px; outline: none; }
107
+ .fr-dz-canvas[hidden], .fr-dz-preview[hidden], .fr-dz-json[hidden] { display: none; }
108
+ .fr-dz-page { margin: 0 auto; background: #fff; box-shadow: 0 1px 3px rgba(14, 23, 38, .16), 0 8px 24px -12px rgba(14, 23, 38, .3); user-select: none; }
109
+ .fr-dz-band { position: relative; }
110
+ .fr-dz-band-head { display: flex; justify-content: space-between; align-items: center; height: 20px; padding: 0 8px; background: #eef1f6; border-top: 1px solid var(--fr-rule); border-bottom: 1px solid var(--fr-rule); font-size: 11px; font-weight: 600; color: var(--fr-ink-2); cursor: pointer; }
111
+ .fr-dz-band-head small { font-weight: 500; font-variant-numeric: tabular-nums; }
112
+ .fr-dz-band.is-selected .fr-dz-band-head { background: #dfe6ff; color: var(--fr-accent); }
113
+ .fr-dz-band-area { position: relative; background-image: radial-gradient(circle, #d3d9e3 .8px, transparent .9px); overflow: visible; }
114
+ .fr-dz-band-resize { height: 5px; margin-top: -2px; position: relative; z-index: 3; cursor: ns-resize; }
115
+ .fr-dz-band-resize:hover { background: var(--fr-accent); opacity: .5; }
116
+ .fr-dz-hint { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: #a6b0c0; font-size: 11px; pointer-events: none; white-space: nowrap; }
117
+ .fr-dz-el { position: absolute; cursor: move; outline: 1px dashed rgba(35, 64, 245, .18); }
118
+ .fr-dz-el:hover { outline-color: rgba(35, 64, 245, .55); }
119
+ .fr-dz-el.is-selected { outline: 1.5px solid var(--fr-accent); z-index: 2; }
120
+ .fr-dz-el.is-line { outline-offset: 2px; }
121
+ .fr-dz-el > * { pointer-events: none; }
122
+ .fr-dz-el > .fr-dz-h, .fr-dz-el > .fr-dz-inline { pointer-events: auto; }
123
+ .fr-dz-text { position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden; white-space: pre-wrap; word-break: break-all; }
124
+ .fr-dz-text small { margin-left: 4px; font-size: 9px; color: #7a8599; font-weight: 400; }
125
+ .fr-dz-expr { color: #1a2fc0; }
126
+ .fr-dz-rect { position: absolute; inset: 0; }
127
+ .fr-dz-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
128
+ .fr-dz-el img { width: 100%; height: 100%; display: block; }
129
+ .fr-dz-ph { position: absolute; inset: 0; display: grid; place-items: center; background: repeating-linear-gradient(45deg, #f4f6fa, #f4f6fa 4px, #eceff5 4px, #eceff5 8px); color: #8a94a6; }
130
+ .fr-dz-h { position: absolute; width: 8px; height: 8px; background: #fff; border: 1.5px solid var(--fr-accent); border-radius: 2px; z-index: 3; }
131
+ .fr-dz-h-nw { left: -4px; top: -4px; cursor: nwse-resize; }
132
+ .fr-dz-h-n { left: calc(50% - 4px); top: -4px; cursor: ns-resize; }
133
+ .fr-dz-h-ne { right: -4px; top: -4px; cursor: nesw-resize; }
134
+ .fr-dz-h-e { right: -4px; top: calc(50% - 4px); cursor: ew-resize; }
135
+ .fr-dz-h-se { right: -4px; bottom: -4px; cursor: nwse-resize; }
136
+ .fr-dz-h-s { left: calc(50% - 4px); bottom: -4px; cursor: ns-resize; }
137
+ .fr-dz-h-sw { left: -4px; bottom: -4px; cursor: nesw-resize; }
138
+ .fr-dz-h-w { left: -4px; top: calc(50% - 4px); cursor: ew-resize; }
139
+ .fr-dz-marquee { position: absolute; border: 1px solid var(--fr-accent); background: rgba(35, 64, 245, .08); pointer-events: none; z-index: 4; }
140
+ .fr-dz-inline { position: absolute; inset: 0; min-height: 24px; z-index: 5; font: 13px/1.4 var(--fr-font); padding: 2px 4px; border: 2px solid var(--fr-accent); resize: none; }
141
+ .fr-dz-preview { flex: 1; min-height: 0; }
142
+ .fr-dz-preview .fr-viewer { border: 0; border-radius: 0; }
143
+ .fr-dz-json { flex: 1; min-height: 0; display: flex; flex-direction: column; }
144
+ .fr-dz-json textarea { flex: 1; border: 0; padding: 14px 16px; font: 12.5px/1.55 ui-monospace, "Cascadia Code", Consolas, monospace; resize: none; outline: none; background: #fbfcfe; color: var(--fr-ink); }
145
+ .fr-dz-json-bar { display: flex; justify-content: flex-end; align-items: center; gap: 12px; padding: 8px 12px; border-top: 1px solid var(--fr-rule); background: var(--fr-panel); }
146
+ .fr-dz-json-err { color: #c8102e; font-size: 12px; }
147
+ .fr-dz-props { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 4px 12px 24px; }
148
+ .fr-dz-sec { grid-column: 1 / -1; margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--fr-rule); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--fr-ink-2); }
149
+ .fr-dz-sec:first-child { border-top: 0; margin-top: 4px; padding-top: 4px; }
150
+ .fr-dz-prop { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
151
+ .fr-dz-prop.is-wide { grid-column: 1 / -1; }
152
+ .fr-dz-prop.is-check { flex-direction: row; align-items: center; gap: 6px; grid-column: 1 / -1; font-size: 12.5px; cursor: pointer; }
153
+ .fr-dz-plabel { font-size: 11.5px; color: var(--fr-ink-2); font-weight: 600; }
154
+ .fr-dz-prop input[type=text], .fr-dz-prop input[type=number], .fr-dz-prop select, .fr-dz-prop textarea { width: 100%; height: 28px; padding: 0 7px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); font: inherit; font-size: 12.5px; color: var(--fr-ink); background: #fff; }
155
+ .fr-dz-prop textarea { height: auto; padding: 5px 7px; font-family: ui-monospace, Consolas, monospace; resize: vertical; }
156
+ .fr-dz-prop input.is-expr { font-family: ui-monospace, Consolas, monospace; }
157
+ .fr-dz-prop :focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 0; }
158
+ .fr-dz-err { color: #c8102e; font-size: 11px; }
159
+ .fr-dz-err:empty { display: none; }
160
+ .fr-dz-color { display: flex; gap: 4px; }
161
+ .fr-dz-color input[type=color] { width: 30px; height: 28px; padding: 1px; border: 1px solid var(--fr-rule); border-radius: var(--fr-radius); background: #fff; flex: none; }
162
+ .fr-dz-toggles { display: flex; gap: 3px; flex-wrap: wrap; }
163
+ .fr-dz-toggle { width: 30px; height: 28px; padding: 0; justify-content: center; border: 1px solid var(--fr-rule); font-weight: 700; }
164
+ .fr-dz-toggle[aria-pressed="true"] { background: #dfe6ff; border-color: var(--fr-accent); color: var(--fr-accent); }
165
+ @media (max-width: 960px) {
166
+ .fr-dz { grid-template-columns: 150px minmax(0, 1fr); }
167
+ .fr-dz-right { display: none; }
168
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freereportjs/core",
3
- "version": "0.1.0",
3
+ "version": "0.9.0",
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",