@topgrid/grid-export 0.3.0 → 0.4.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
@@ -1,12 +1,12 @@
1
1
  import { Table } from '@tanstack/react-table';
2
- import { E as ExcelExportOptions, C as CSVExportOptions, P as PDFExportOptions, a as ClipboardOptions, b as PrintOptions, c as ExcelColumn, d as ExportRowsOptions } from './types-BCeqH-13.js';
3
- export { D as DownloadExcelOptions, e as EmptyBehavior, f as ExportScope } from './types-BCeqH-13.js';
2
+ import { E as ExcelExportOptions, C as CSVExportOptions, P as PDFExportOptions, a as ClipboardOptions, b as PrintOptions, c as ExcelColumn, d as ExportRowsOptions, e as ExcelSheet, M as MultiSheetOptions } from './types-yAFLGyyY.js';
3
+ export { D as DownloadExcelOptions, f as EmptyBehavior, g as ExportScope } from './types-yAFLGyyY.js';
4
4
 
5
5
  /**
6
6
  * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.
7
7
  *
8
8
  * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
9
- * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)
9
+ * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior, columnFormats, columnWidths)
10
10
  * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)
11
11
  *
12
12
  * @remarks
@@ -14,6 +14,10 @@ export { D as DownloadExcelOptions, e as EmptyBehavior, f as ExportScope } from
14
14
  * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.
15
15
  * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).
16
16
  *
17
+ * **셀 서식(MOD-GRID-25)**: `columnFormats` 는 네이티브 Excel number-format(`.z`)을 적용해
18
+ * 셀이 Excel 안에서 numeric·정렬가능하게 유지됩니다. 폰트/배경색은 community
19
+ * `xlsx@0.18.5` 가 write 시 미지원(round-trip 시 스트립)이라 제공하지 않습니다.
20
+ *
17
21
  * @example
18
22
  * // 기본 사용 (filtered 행)
19
23
  * exportToExcel(table, { fileName: '데이터.xlsx' });
@@ -26,6 +30,13 @@ export { D as DownloadExcelOptions, e as EmptyBehavior, f as ExportScope } from
26
30
  * scope: 'selected',
27
31
  * emptyBehavior: 'empty',
28
32
  * });
33
+ *
34
+ * @example
35
+ * // 네이티브 숫자서식 + 컬럼 폭
36
+ * exportToExcel(table, {
37
+ * columnFormats: { price: '#,##0.00', orderedAt: 'yyyy-mm-dd' },
38
+ * columnWidths: { name: 30 },
39
+ * });
29
40
  */
30
41
  declare function exportToExcel<TData>(table: Table<TData>, options?: ExcelExportOptions): void;
31
42
 
@@ -135,4 +146,26 @@ declare function printGrid<TData>(table: Table<TData>, options?: PrintOptions):
135
146
  */
136
147
  declare function exportRowsToExcel<TData extends Record<string, unknown>>(rows: TData[], columns: ExcelColumn[], options?: ExportRowsOptions): void;
137
148
 
138
- export { CSVExportOptions, ClipboardOptions, ExcelColumn, ExcelExportOptions, ExportRowsOptions, PDFExportOptions, PrintOptions, copyToClipboard, exportRowsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
149
+ /**
150
+ * 여러 TanStack Table 을 **하나의 Excel 워크북**(여러 시트)으로 export·다운로드한다.
151
+ * (MOD-GRID-25 G-2 — AG Grid/DevExpress 다중 시트 export 격차 해소)
152
+ *
153
+ * 각 시트는 `exportToExcel`(단일 시트)과 동일한 빌더(`buildGridWorksheet`)를 재사용하므로
154
+ * 헤더 merge·scope·네이티브 숫자서식(`.z`)·컬럼 폭 동작이 단일 시트와 일관된다.
155
+ *
156
+ * @param sheets 시트 정의 배열 (`{ name, table, scope?, columnFormats?, columnWidths? }`)
157
+ * @param options 파일명 옵션
158
+ * @returns void (동기 — xlsx.writeFile)
159
+ *
160
+ * @example
161
+ * exportSheetsToExcel(
162
+ * [
163
+ * { name: '주문', table: ordersTable, columnFormats: { total: '#,##0' } },
164
+ * { name: '고객', table: customersTable, scope: 'selected' },
165
+ * ],
166
+ * { fileName: '월간보고.xlsx' },
167
+ * );
168
+ */
169
+ declare function exportSheetsToExcel(sheets: ExcelSheet[], options?: MultiSheetOptions): void;
170
+
171
+ export { CSVExportOptions, ClipboardOptions, ExcelColumn, ExcelExportOptions, ExcelSheet, ExportRowsOptions, MultiSheetOptions, PDFExportOptions, PrintOptions, copyToClipboard, exportRowsToExcel, exportSheetsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import * as XLSX2 from 'xlsx';
1
+ import * as XLSX4 from 'xlsx';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -35,8 +35,6 @@ function getRowsByScope(table, scope) {
35
35
  }
36
36
  return table.getFilteredRowModel().rows;
37
37
  }
38
-
39
- // src/exportToExcel.ts
40
38
  function resolveHeaderText(headerValue) {
41
39
  if (typeof headerValue === "string") {
42
40
  return headerValue;
@@ -89,12 +87,56 @@ function buildHeaderRows(table) {
89
87
  }
90
88
  return { headerRows, merges };
91
89
  }
90
+ function buildGridWorksheet(params) {
91
+ const {
92
+ headerRows,
93
+ merges,
94
+ dataRows,
95
+ leafColumnIds,
96
+ columnFormats,
97
+ columnWidths
98
+ } = params;
99
+ const aoa = [...headerRows, ...dataRows];
100
+ const ws = XLSX4.utils.aoa_to_sheet(aoa);
101
+ if (merges.length > 0) {
102
+ ws["!merges"] = merges;
103
+ }
104
+ if (columnFormats) {
105
+ const headerOffset = headerRows.length;
106
+ for (let c = 0; c < leafColumnIds.length; c++) {
107
+ const fmt = columnFormats[leafColumnIds[c]];
108
+ if (!fmt) continue;
109
+ for (let r = 0; r < dataRows.length; r++) {
110
+ const addr = XLSX4.utils.encode_cell({ r: headerOffset + r, c });
111
+ const cell = ws[addr];
112
+ if (cell && cell.t === "n") {
113
+ cell.z = fmt;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ if (columnWidths) {
119
+ const hasAny = leafColumnIds.some(
120
+ (id) => columnWidths[id] !== void 0
121
+ );
122
+ if (hasAny) {
123
+ ws["!cols"] = leafColumnIds.map(
124
+ (id) => columnWidths[id] !== void 0 ? { wch: columnWidths[id] } : {}
125
+ );
126
+ }
127
+ }
128
+ return ws;
129
+ }
130
+
131
+ // src/exportToExcel.ts
92
132
  function exportToExcel(table, options) {
93
133
  const {
94
134
  fileName = "export.xlsx",
95
135
  sheetName = "Sheet1",
96
136
  scope = "filtered",
97
- emptyBehavior = "skip"
137
+ emptyBehavior = "skip",
138
+ columnFormats,
139
+ columnWidths
98
140
  } = options ?? {};
99
141
  const rows = getRowsByScope(table, scope);
100
142
  if (rows.length === 0 && emptyBehavior === "skip") {
@@ -108,15 +150,19 @@ function exportToExcel(table, options) {
108
150
  return value !== void 0 && value !== null ? value : "";
109
151
  })
110
152
  );
111
- const aoa = [...headerRows, ...dataRows];
112
- const ws = XLSX2.utils.aoa_to_sheet(aoa);
113
- if (merges.length > 0) {
114
- ws["!merges"] = merges;
115
- }
116
- const wb = XLSX2.utils.book_new();
117
- XLSX2.utils.book_append_sheet(wb, ws, sheetName);
153
+ const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);
154
+ const ws = buildGridWorksheet({
155
+ headerRows,
156
+ merges,
157
+ dataRows,
158
+ leafColumnIds,
159
+ columnFormats,
160
+ columnWidths
161
+ });
162
+ const wb = XLSX4.utils.book_new();
163
+ XLSX4.utils.book_append_sheet(wb, ws, sheetName);
118
164
  const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
119
- XLSX2.writeFile(wb, finalFileName);
165
+ XLSX4.writeFile(wb, finalFileName);
120
166
  }
121
167
 
122
168
  // src/exportToCSV.ts
@@ -229,7 +275,11 @@ function escapeTsvValue(value) {
229
275
  return value.replace(/[\t\r\n]/g, " ");
230
276
  }
231
277
  async function copyToClipboard(table, options) {
232
- const { scope = "filtered", emptyBehavior = "skip" } = options ?? {};
278
+ const {
279
+ scope = "filtered",
280
+ emptyBehavior = "skip",
281
+ includeHeader = true
282
+ } = options ?? {};
233
283
  const rows = getRowsByScope(table, scope);
234
284
  if (rows.length === 0 && emptyBehavior === "skip") {
235
285
  console.warn("[grid-export] copyToClipboard: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -248,7 +298,9 @@ async function copyToClipboard(table, options) {
248
298
  return escapeTsvValue(str);
249
299
  }).join(" ")
250
300
  );
251
- const tsvString = [headerRow, ...dataRows].join("\n");
301
+ const tsvString = (includeHeader ? [headerRow, ...dataRows] : dataRows).join(
302
+ "\n"
303
+ );
252
304
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
253
305
  await navigator.clipboard.writeText(tsvString);
254
306
  } else {
@@ -362,20 +414,51 @@ function exportRowsToExcel(rows, columns, options) {
362
414
  (row) => columns.map((col) => formatValue(row[col.key], col.format))
363
415
  );
364
416
  const aoa = rows.length === 0 ? [header] : [header, ...dataRows];
365
- const ws = XLSX2.utils.aoa_to_sheet(aoa);
417
+ const ws = XLSX4.utils.aoa_to_sheet(aoa);
366
418
  ws["!cols"] = columns.map((c) => ({ wch: c.width ?? 15 }));
367
- const headerRange = XLSX2.utils.decode_range(ws["!ref"] ?? "A1");
419
+ const headerRange = XLSX4.utils.decode_range(ws["!ref"] ?? "A1");
368
420
  for (let c = headerRange.s.c; c <= headerRange.e.c; c++) {
369
- const cellAddr = XLSX2.utils.encode_cell({ r: 0, c });
421
+ const cellAddr = XLSX4.utils.encode_cell({ r: 0, c });
370
422
  if (!ws[cellAddr]) continue;
371
423
  ws[cellAddr].s = { font: { bold: true }, fill: { fgColor: { rgb: "F3F4F6" } } };
372
424
  }
373
- const wb = XLSX2.utils.book_new();
374
- XLSX2.utils.book_append_sheet(wb, ws, sheetName);
425
+ const wb = XLSX4.utils.book_new();
426
+ XLSX4.utils.book_append_sheet(wb, ws, sheetName);
375
427
  const ext = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
376
- XLSX2.writeFile(wb, ext);
428
+ XLSX4.writeFile(wb, ext);
429
+ }
430
+ function exportSheetsToExcel(sheets, options) {
431
+ const { fileName = "export.xlsx" } = options ?? {};
432
+ if (sheets.length === 0) {
433
+ console.warn("[grid-export] exportSheetsToExcel: \uC2DC\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
434
+ return;
435
+ }
436
+ const wb = XLSX4.utils.book_new();
437
+ for (const sheet of sheets) {
438
+ const { name, table, scope = "filtered", columnFormats, columnWidths } = sheet;
439
+ const rows = getRowsByScope(table, scope);
440
+ const { headerRows, merges } = buildHeaderRows(table);
441
+ const dataRows = rows.map(
442
+ (row) => row.getVisibleCells().map((cell) => {
443
+ const value = cell.getValue();
444
+ return value !== void 0 && value !== null ? value : "";
445
+ })
446
+ );
447
+ const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);
448
+ const ws = buildGridWorksheet({
449
+ headerRows,
450
+ merges,
451
+ dataRows,
452
+ leafColumnIds,
453
+ columnFormats,
454
+ columnWidths
455
+ });
456
+ XLSX4.utils.book_append_sheet(wb, ws, name);
457
+ }
458
+ const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
459
+ XLSX4.writeFile(wb, finalFileName);
377
460
  }
378
461
 
379
- export { copyToClipboard, exportRowsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
462
+ export { copyToClipboard, exportRowsToExcel, exportSheetsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
380
463
  //# sourceMappingURL=index.mjs.map
381
464
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/loadKoreanFont.ts","../src/internal/getRowsByScope.ts","../src/exportToExcel.ts","../src/exportToCSV.ts","../src/exportToPdf.ts","../src/copyToClipboard.ts","../src/printGrid.ts","../src/exportRowsToExcel.ts"],"names":["XLSX","loadKoreanFont"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { default as jsPDFType } from 'jspdf';\r\n\r\n/**\r\n * jsPDF 인스턴스에 한국어 폰트를 등록한다.\r\n *\r\n * @param pdf - jsPDF 인스턴스\r\n *\r\n * @remarks\r\n * **⚠️ STUB 구현 (W1 리스크)**\r\n *\r\n * 실 폰트 base64 데이터가 미확보 상태이므로 현재 no-op 로 동작합니다.\r\n * `fontFamily: 'korean'` 사용 시 Helvetica(기본 폰트)로 fallback되며 한국어 글자가 깨질 수 있습니다.\r\n *\r\n * **실 구현 방법 (V1 목표)**:\r\n * 1. 폰트 라이선스 확인: NotoSansKR (OFL 1.1 — 임베딩 허용) 또는 NanumGothic (OFL 1.1)\r\n * 2. 폰트 파일을 base64로 변환: `Buffer.from(fs.readFileSync('NotoSansKR.ttf')).toString('base64')`\r\n * 3. 아래 주석 해제 + base64 문자열 삽입:\r\n * ```\r\n * const fontBase64 = '<base64-string>';\r\n * pdf.addFileToVFS('NotoSansKR.ttf', fontBase64);\r\n * pdf.addFont('NotoSansKR.ttf', 'NotoSansKR', 'normal');\r\n * pdf.setFont('NotoSansKR');\r\n * ```\r\n *\r\n * @see https://github.com/parallax/jsPDF#use-of-unicode-characters--utf-8\r\n */\r\nexport async function loadKoreanFont(pdf: jsPDFType): Promise<void> {\r\n // TODO(V1): 폰트 base64 데이터 확보 후 아래 구현 활성화\r\n // 폰트 파일 라이선스: NotoSansKR OFL 1.1 또는 NanumGothic OFL 1.1 권장\r\n // 참고: spec Section 11 W1, Section 12 V1\r\n\r\n // 현재 stub 상태 — pdf 파라미터는 V1 구현 시 사용됨\r\n void pdf;\r\n\r\n console.warn(\r\n '[grid-export] exportToPdf: fontFamily: \"korean\" 옵션은 stub 상태입니다. ' +\r\n '한국어 글자가 깨질 수 있습니다. ' +\r\n 'loadKoreanFont.ts V1 구현 완료 후 사용하세요. (spec Section 12 V1)',\r\n );\r\n}\r\n","import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import * as XLSX from 'xlsx';\r\nimport type { Table } from '@tanstack/react-table';\r\nimport type { ExcelExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\r\n */\r\nfunction resolveHeaderText(headerValue: unknown): string {\r\n if (typeof headerValue === 'string') {\r\n return headerValue;\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * TanStack Table 의 헤더 그룹을 순회하여\r\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\r\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\r\n * 을 반환한다.\r\n *\r\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\r\n */\r\nfunction buildHeaderRows<TData>(table: Table<TData>): {\r\n headerRows: unknown[][];\r\n merges: XLSX.Range[];\r\n} {\r\n const headerGroups = table.getHeaderGroups();\r\n const merges: XLSX.Range[] = [];\r\n const headerRows: unknown[][] = [];\r\n\r\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n const row: unknown[] = [];\r\n\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n if (header.isPlaceholder) {\r\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\r\n row.push('');\r\n } else {\r\n const text = resolveHeaderText(\r\n typeof header.column.columnDef.header === 'string'\r\n ? header.column.columnDef.header\r\n : header.column.id,\r\n );\r\n row.push(text);\r\n\r\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\r\n if (header.colSpan > 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n\r\n headerRows.push(row);\r\n }\r\n\r\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\r\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\r\n if (headerGroups.length > 1) {\r\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\r\n if (!header.isPlaceholder && header.colSpan === 1) {\r\n if (rowIdx + 1 <= headerGroups.length - 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: headerGroups.length - 1, c: colIdx },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n }\r\n }\r\n\r\n return { headerRows, merges };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)\r\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\r\n *\r\n * @remarks\r\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\r\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\r\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\r\n *\r\n * @example\r\n * // 기본 사용 (filtered 행)\r\n * exportToExcel(table, { fileName: '데이터.xlsx' });\r\n *\r\n * @example\r\n * // 선택 행 + 다중행 헤더\r\n * exportToExcel(table, {\r\n * fileName: '선택데이터.xlsx',\r\n * sheetName: '선택목록',\r\n * scope: 'selected',\r\n * emptyBehavior: 'empty',\r\n * });\r\n */\r\nexport function exportToExcel<TData>(\r\n table: Table<TData>,\r\n options?: ExcelExportOptions,\r\n): void {\r\n const {\r\n fileName = 'export.xlsx',\r\n sheetName = 'Sheet1',\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API만)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-01, EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\r\n const { headerRows, merges } = buildHeaderRows(table);\r\n\r\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\r\n const dataRows: unknown[][] = rows.map((row) =>\r\n row.getVisibleCells().map((cell) => {\r\n const value = cell.getValue();\r\n return value !== undefined && value !== null ? value : '';\r\n }),\r\n );\r\n\r\n // 5) AOA sheet 생성 (헤더 + 데이터)\r\n const aoa: unknown[][] = [...headerRows, ...dataRows];\r\n const ws = XLSX.utils.aoa_to_sheet(aoa);\r\n\r\n // 6) merge cells 적용 (다중행 헤더 있을 때, AC-003)\r\n if (merges.length > 0) {\r\n ws['!merges'] = merges;\r\n }\r\n\r\n // 7) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\r\n const wb = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\r\n const finalFileName = fileName.endsWith('.xlsx')\r\n ? fileName\r\n : `${fileName}.xlsx`;\r\n XLSX.writeFile(wb, finalFileName);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { CSVExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helper — RFC 4180 이스케이프\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * RFC 4180 §2: 구분자/큰따옴표/개행 포함 시 큰따옴표 래핑 + 내부 따옴표 이중화\r\n * (외부 라이브러리 0 — 순수 string 조작, AC-001, AC-003)\r\n */\r\nfunction escapeCsvValue(value: string, delimiter: string): string {\r\n const needsQuoting =\r\n value.includes(delimiter) ||\r\n value.includes('\"') ||\r\n value.includes('\\n') ||\r\n value.includes('\\r');\r\n if (!needsQuoting) return value;\r\n return '\"' + value.split('\"').join('\"\"') + '\"';\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 CSV 파일을 생성·다운로드한다.\r\n * UTF-8 BOM() 포함 — 한국어 Excel 정상 표시 (AC-004).\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options CSV export 옵션 (fileName, scope, delimiter, emptyBehavior)\r\n * @returns void (순수 string 조작 + createObjectURL — 외부 라이브러리 0, D5)\r\n *\r\n * @remarks\r\n * **브라우저 전용**: URL.createObjectURL / document.createElement — SSR 환경 불가.\r\n *\r\n * @example\r\n * // 기본 사용 (filtered 행, 쉼표 구분자)\r\n * exportToCSV(table, { fileName: '데이터.csv' });\r\n *\r\n * @example\r\n * // TSV + 선택 행\r\n * exportToCSV(table, { fileName: '선택.tsv', delimiter: '\\t', scope: 'selected' });\r\n */\r\nexport function exportToCSV<TData>(\r\n table: Table<TData>,\r\n options?: CSVExportOptions,\r\n): void {\r\n const {\r\n fileName = 'export.csv',\r\n scope = 'filtered',\r\n delimiter = ',',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-02)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToCSV: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 추출 — 리프 헤더만 (단일행 CSV, CSV는 다중행 헤더 미지원)\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerRow = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return escapeCsvValue(text, delimiter);\r\n })\r\n .join(delimiter);\r\n\r\n // 4) 데이터 행 추출 (EC-04: null/undefined → 빈 문자열)\r\n const dataRows = rows.map((row) =>\r\n row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str =\r\n value !== null && value !== undefined ? String(value) : '';\r\n return escapeCsvValue(str, delimiter);\r\n })\r\n .join(delimiter),\r\n );\r\n\r\n // 5) CSV 문자열 조립 (AC-003 RFC 4180: CRLF 행 구분)\r\n const lines = [headerRow, ...dataRows];\r\n const csvString = lines.join('\\r\\n');\r\n\r\n // 6) BOM + Blob 생성 → 다운로드 (AC-004:  UTF-8 BOM → 한국어 Excel 정상)\r\n const bom = '';\r\n const blob = new Blob([bom + csvString], {\r\n type: 'text/csv;charset=utf-8;',\r\n });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement('a');\r\n link.href = url;\r\n\r\n // 7) fileName 확장자 자동 추가 (EC-05: .csv 또는 .tsv 없으면 .csv 추가)\r\n const finalFileName =\r\n fileName.endsWith('.csv') || fileName.endsWith('.tsv')\r\n ? fileName\r\n : `${fileName}.csv`;\r\n link.download = finalFileName;\r\n link.click();\r\n URL.revokeObjectURL(url);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { PDFExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 PDF 파일을 생성·다운로드한다.\r\n * jspdf + jspdf-autotable을 optional peer로 dynamic import하여 사용.\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options - PDF export 옵션 (fileName, title, scope, orientation, fontFamily, emptyBehavior)\r\n * @returns Promise<void> — jspdf dynamic import 후 완료\r\n * @throws Error jspdf 또는 jspdf-autotable이 설치되지 않은 경우\r\n *\r\n * @remarks\r\n * **peer 설치 필요**: jspdf + jspdf-autotable은 optional peerDependency.\r\n * 사용 전 `npm install jspdf jspdf-autotable` 실행 필요.\r\n *\r\n * **한국어 폰트**: `fontFamily: 'korean'` 옵션은 W1 리스크 (loadKoreanFont.ts stub 상태).\r\n * 실 폰트 base64 데이터 확보 + 라이선스 확인 후 loadKoreanFont.ts 구현 완료 필요.\r\n *\r\n * @example\r\n * // 기본 사용 (portrait, filtered, Helvetica)\r\n * await exportToPdf(table, { fileName: '보고서.pdf' });\r\n *\r\n * @example\r\n * // 가로 방향 + 전체 행 + 제목\r\n * await exportToPdf(table, {\r\n * fileName: '전체데이터.pdf',\r\n * title: '2026년 데이터 목록',\r\n * scope: 'all',\r\n * orientation: 'l',\r\n * emptyBehavior: 'empty',\r\n * });\r\n */\r\nexport async function exportToPdf<TData>(\r\n table: Table<TData>,\r\n options?: PDFExportOptions,\r\n): Promise<void> {\r\n const {\r\n fileName = 'export.pdf',\r\n title,\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n orientation = 'p',\r\n fontFamily = 'default',\r\n } = options ?? {};\r\n\r\n // 1. jspdf dynamic import (optional peer — 미설치 시 명확한 Error)\r\n let jsPDF: Awaited<typeof import('jspdf')>['default'];\r\n try {\r\n const mod = await import('jspdf');\r\n jsPDF = mod.default;\r\n } catch {\r\n throw new Error(\r\n '[exportToPdf] jspdf is not installed. Run: npm install jspdf jspdf-autotable',\r\n );\r\n }\r\n\r\n // 2. jspdf-autotable dynamic import (optional peer — 미설치 시 명확한 Error)\r\n try {\r\n await import('jspdf-autotable');\r\n } catch {\r\n throw new Error(\r\n '[exportToPdf] jspdf-autotable is not installed. Run: npm install jspdf jspdf-autotable',\r\n );\r\n }\r\n\r\n // 3. 행 수집 (getRowsByScope 재사용 — G-002 추출 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToPdf: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 4. 다중행 헤더 구성 (AC-005: isPlaceholder + colSpan 처리)\r\n const headerGroups = table.getHeaderGroups();\r\n const head: string[][] = headerGroups.map((hg) =>\r\n hg.headers.map((h) => {\r\n if (h.isPlaceholder) return '';\r\n return typeof h.column.columnDef.header === 'string'\r\n ? h.column.columnDef.header\r\n : h.column.id;\r\n }),\r\n );\r\n\r\n // 5. 데이터 행 구성 (리프 헤더 순서 기준 — D7)\r\n const leafHeaders = table.getLeafHeaders();\r\n const body: string[][] = rows.map((row) => {\r\n const cells = row.getVisibleCells();\r\n return leafHeaders.map((lh) => {\r\n const cell = cells.find((c) => c.column.id === lh.column.id);\r\n return cell ? String(cell.getValue() ?? '') : '';\r\n });\r\n });\r\n\r\n // 6. jsPDF 인스턴스 생성\r\n const doc = new jsPDF({ orientation, unit: 'pt', format: 'a4' });\r\n\r\n // 7. 한국어 폰트 로드 (fontFamily === 'korean' 시 dynamic import)\r\n if (fontFamily === 'korean') {\r\n const { loadKoreanFont } = await import('./internal/loadKoreanFont');\r\n await loadKoreanFont(doc);\r\n }\r\n\r\n // 8. title 행 (있으면)\r\n if (title) {\r\n doc.text(title, 14, 20);\r\n }\r\n\r\n // 9. autoTable 렌더링\r\n // @ts-expect-error jspdf-autotable extends jsPDF prototype at runtime (W2 — see spec Section 11)\r\n doc.autoTable({ head, body, startY: title ? 30 : 14 });\r\n\r\n // 10. 파일명 정규화 + 다운로드\r\n const normalized = fileName.endsWith('.pdf') ? fileName : `${fileName}.pdf`;\r\n doc.save(normalized);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { ClipboardOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helper — TSV 이스케이프\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TSV 셀 값 이스케이프: 탭/개행/캐리지리턴 → 공백 치환 (D5)\r\n * TSV는 RFC 표준 quoting 없음 — delimiter(탭) 포함 값은 공백으로 대체가 Excel 호환 최우선 전략.\r\n */\r\nfunction escapeTsvValue(value: string): string {\r\n return value.replace(/[\\t\\r\\n]/g, ' ');\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 데이터를 TSV 포맷으로 클립보드에 복사한다.\r\n * TSV(탭 구분, 줄바꿈 행 구분) — Excel 붙여넣기 호환.\r\n *\r\n * navigator.clipboard 미지원 환경: document.execCommand('copy') fallback 시도.\r\n * fallback도 실패 시 Error('[grid-export] copyToClipboard: Clipboard API not supported') throw.\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options 클립보드 복사 옵션 (scope, emptyBehavior)\r\n * @returns Promise<void> — navigator.clipboard.writeText 는 async\r\n *\r\n * @example\r\n * await copyToClipboard(table);\r\n *\r\n * @example\r\n * await copyToClipboard(table, { scope: 'selected' });\r\n */\r\nexport async function copyToClipboard<TData>(\r\n table: Table<TData>,\r\n options?: ClipboardOptions,\r\n): Promise<void> {\r\n const { scope = 'filtered', emptyBehavior = 'skip' } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] copyToClipboard: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 행 구성 — 리프 헤더만\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerRow = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return escapeTsvValue(text);\r\n })\r\n .join('\\t');\r\n\r\n // 4) 데이터 행 구성 (EC-07: null/undefined → 빈 문자열, EC-01/EC-02: 탭/개행 → 공백)\r\n const dataRows = rows.map((row) =>\r\n row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str = value !== null && value !== undefined ? String(value) : '';\r\n return escapeTsvValue(str);\r\n })\r\n .join('\\t'),\r\n );\r\n\r\n // 5) TSV 문자열 조립 (헤더 + 데이터, 줄바꿈 행 구분)\r\n const tsvString = [headerRow, ...dataRows].join('\\n');\r\n\r\n // 6) 클립보드 쓰기 (navigator.clipboard 우선, execCommand fallback — D6, EC-04)\r\n if (\r\n typeof navigator !== 'undefined' &&\r\n navigator.clipboard &&\r\n typeof navigator.clipboard.writeText === 'function'\r\n ) {\r\n await navigator.clipboard.writeText(tsvString);\r\n } else {\r\n // fallback: document.execCommand('copy') — HTTP 개발 환경 또는 구형 브라우저 (EC-04)\r\n const textarea = document.createElement('textarea');\r\n textarea.value = tsvString;\r\n textarea.style.position = 'fixed';\r\n textarea.style.opacity = '0';\r\n document.body.appendChild(textarea);\r\n textarea.select();\r\n const success = document.execCommand('copy');\r\n document.body.removeChild(textarea);\r\n if (!success) {\r\n throw new Error('[grid-export] copyToClipboard: Clipboard API not supported');\r\n }\r\n }\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { PrintOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n/**\r\n * TanStack Table 데이터를 새 팝업 창에 HTML 테이블로 렌더링하여 인쇄 대화상자를 연다.\r\n * 순수 Web API 전용 (window.open + document.write + window.print).\r\n *\r\n * 팝업 차단 환경: console.warn 후 즉시 반환 (D7 — throw 하지 않음).\r\n * printGrid 자체는 동기 반환 (void). 실제 print 발화는 popup.onload 내에서 비동기 실행 (D4).\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options 인쇄 옵션 (title, scope, orientation, emptyBehavior)\r\n * @returns void\r\n *\r\n * @example\r\n * printGrid(table);\r\n *\r\n * @example\r\n * printGrid(table, { title: '계약 목록', scope: 'filtered', orientation: 'l' });\r\n */\r\nexport function printGrid<TData>(\r\n table: Table<TData>,\r\n options?: PrintOptions,\r\n): void {\r\n const {\r\n title,\r\n scope = 'filtered',\r\n orientation = 'p',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] printGrid: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 HTML 구성\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerHtml = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return `<th>${text}</th>`;\r\n })\r\n .join('');\r\n\r\n // 4) 데이터 행 HTML 구성 (EC-07: null/undefined → 빈 문자열)\r\n const bodyHtml = rows\r\n .map((row) => {\r\n const cells = row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str = value !== null && value !== undefined ? String(value) : '';\r\n return `<td>${str}</td>`;\r\n })\r\n .join('');\r\n return `<tr>${cells}</tr>`;\r\n })\r\n .join('');\r\n\r\n // 5) HTML 문서 구성\r\n const orientationCss = orientation === 'l' ? 'landscape' : 'portrait';\r\n const titleHtml = title ? `<h2>${title}</h2>` : '';\r\n\r\n const html = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<title>${title ?? ''}</title>\r\n<style>\r\n @page { size: ${orientationCss}; }\r\n body { font-family: sans-serif; font-size: 12px; margin: 16px; }\r\n h2 { font-size: 16px; margin-bottom: 12px; }\r\n table { border-collapse: collapse; width: 100%; }\r\n th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; page-break-inside: avoid; }\r\n th { background: #f0f0f0; }\r\n @media print {\r\n body { margin: 0; }\r\n }\r\n</style>\r\n</head>\r\n<body>\r\n${titleHtml}\r\n<table>\r\n <thead><tr>${headerHtml}</tr></thead>\r\n <tbody>${bodyHtml}</tbody>\r\n</table>\r\n</body>\r\n</html>`;\r\n\r\n // 6) 팝업 창 열기 (EC-05: null 반환 = 팝업 차단 → D7 warn + return)\r\n const popup = window.open('', '_blank');\r\n if (!popup) {\r\n console.warn(\r\n '[grid-export] printGrid: 팝업이 차단되었습니다. 브라우저 설정에서 팝업을 허용 후 재시도하세요.',\r\n );\r\n return;\r\n }\r\n\r\n // 7) EC-06: onload 등록을 document.write **이전**에 해야 함 (Firefox/Safari 호환)\r\n // about:blank의 load 이벤트는 document.write 시점에 이미 발화될 수 있으므로\r\n // write 이후 등록 시 핸들러가 실행되지 않을 위험이 있음.\r\n popup.onload = () => {\r\n popup.print();\r\n popup.close();\r\n };\r\n\r\n popup.document.write(html);\r\n popup.document.close();\r\n}\r\n","/**\r\n * exportRowsToExcel — 행 배열 기반 Excel export (ADR-005, E-1 + F-1)\r\n *\r\n * TanStack Table 인스턴스 없이 raw row array + ExcelColumn[] 로 .xlsx 생성.\r\n * 거동 패리티 (spec §3 B1/B2/B3):\r\n * B1 — 컬럼 width: ws['!cols'] 적용 (ExcelColumn.width ?? 15)\r\n * B2 — 헤더 styling: bold + 회색 fill (F3F4F6) — note: xlsx community edition has limited style support\r\n * B3 — formatValue: date/datetime/number/currency 포맷 (ko-KR locale)\r\n *\r\n * @see ExcelColumn\r\n * @see ExportRowsOptions\r\n */\r\nimport * as XLSX from 'xlsx';\r\nimport type { ExcelColumn, ExportRowsOptions } from './types';\r\n\r\n// ── B3: 셀 값 포맷터 ──────────────────────────────────────────────────────\r\n\r\nfunction formatValue(value: unknown, format?: ExcelColumn['format']): unknown {\r\n if (value == null) return '';\r\n if (format === 'date' || format === 'datetime') {\r\n const d = new Date(value as string);\r\n if (isNaN(d.getTime())) return String(value);\r\n return format === 'datetime'\r\n ? d.toLocaleString('ko-KR')\r\n : d.toLocaleDateString('ko-KR');\r\n }\r\n if (format === 'number' || format === 'currency') {\r\n const n = Number(value);\r\n return isNaN(n) ? value : n;\r\n }\r\n return value;\r\n}\r\n\r\n// ── 공개 API ──────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * 행 배열을 Excel 파일(.xlsx)로 다운로드한다.\r\n *\r\n * TanStack `Table<TData>` 인스턴스 없이 사용 가능.\r\n * `@topgrid/grid-export` 의 `exportToExcel(table, options)` 와 평행 지원 (ADR-005 옵션 A).\r\n *\r\n * @example\r\n * exportRowsToExcel(rows, columns, { fileName: '보고서_2026.xlsx' });\r\n *\r\n * @param rows 내보낼 데이터 행 배열\r\n * @param columns 컬럼 정의 배열 (key / header / width? / format?)\r\n * @param options 파일명·시트명·emptyBehavior 옵션\r\n */\r\nexport function exportRowsToExcel<TData extends Record<string, unknown>>(\r\n rows: TData[],\r\n columns: ExcelColumn[],\r\n options?: ExportRowsOptions,\r\n): void {\r\n const {\r\n fileName = 'export.xlsx',\r\n sheetName = 'Sheet1',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[exportRowsToExcel] rows is empty — skipping file creation (emptyBehavior: \"skip\")');\r\n return;\r\n }\r\n\r\n // ── 헤더 + 데이터 AOA 구성 ─────────────────────────────────────────────\r\n const header = columns.map((c) => c.header);\r\n const dataRows = rows.map((row) =>\r\n columns.map((col) => formatValue(row[col.key], col.format))\r\n );\r\n const aoa = rows.length === 0 ? [header] : [header, ...dataRows];\r\n\r\n const ws = XLSX.utils.aoa_to_sheet(aoa);\r\n\r\n // ── B1: 컬럼 width ─────────────────────────────────────────────────────\r\n ws['!cols'] = columns.map((c) => ({ wch: c.width ?? 15 }));\r\n\r\n // ── B2: 헤더 행 styling (굵게 + 회색 fill) ────────────────────────────\r\n // note: xlsx community edition has limited style support\r\n const headerRange = XLSX.utils.decode_range(ws['!ref'] ?? 'A1');\r\n for (let c = headerRange.s.c; c <= headerRange.e.c; c++) {\r\n const cellAddr = XLSX.utils.encode_cell({ r: 0, c });\r\n if (!ws[cellAddr]) continue;\r\n ws[cellAddr].s = { font: { bold: true }, fill: { fgColor: { rgb: 'F3F4F6' } } };\r\n }\r\n\r\n // ── 워크북 생성 + 다운로드 ─────────────────────────────────────────────\r\n const wb = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\r\n\r\n const ext = fileName.endsWith('.xlsx') ? fileName : `${fileName}.xlsx`;\r\n XLSX.writeFile(wb, ext);\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/internal/loadKoreanFont.ts","../src/internal/getRowsByScope.ts","../src/internal/buildHeaderRows.ts","../src/internal/buildGridWorksheet.ts","../src/exportToExcel.ts","../src/exportToCSV.ts","../src/exportToPdf.ts","../src/copyToClipboard.ts","../src/printGrid.ts","../src/exportRowsToExcel.ts","../src/exportSheetsToExcel.ts"],"names":["XLSX2","XLSX3","loadKoreanFont","XLSX5"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { default as jsPDFType } from 'jspdf';\r\n\r\n/**\r\n * jsPDF 인스턴스에 한국어 폰트를 등록한다.\r\n *\r\n * @param pdf - jsPDF 인스턴스\r\n *\r\n * @remarks\r\n * **⚠️ STUB 구현 (W1 리스크)**\r\n *\r\n * 실 폰트 base64 데이터가 미확보 상태이므로 현재 no-op 로 동작합니다.\r\n * `fontFamily: 'korean'` 사용 시 Helvetica(기본 폰트)로 fallback되며 한국어 글자가 깨질 수 있습니다.\r\n *\r\n * **실 구현 방법 (V1 목표)**:\r\n * 1. 폰트 라이선스 확인: NotoSansKR (OFL 1.1 — 임베딩 허용) 또는 NanumGothic (OFL 1.1)\r\n * 2. 폰트 파일을 base64로 변환: `Buffer.from(fs.readFileSync('NotoSansKR.ttf')).toString('base64')`\r\n * 3. 아래 주석 해제 + base64 문자열 삽입:\r\n * ```\r\n * const fontBase64 = '<base64-string>';\r\n * pdf.addFileToVFS('NotoSansKR.ttf', fontBase64);\r\n * pdf.addFont('NotoSansKR.ttf', 'NotoSansKR', 'normal');\r\n * pdf.setFont('NotoSansKR');\r\n * ```\r\n *\r\n * @see https://github.com/parallax/jsPDF#use-of-unicode-characters--utf-8\r\n */\r\nexport async function loadKoreanFont(pdf: jsPDFType): Promise<void> {\r\n // TODO(V1): 폰트 base64 데이터 확보 후 아래 구현 활성화\r\n // 폰트 파일 라이선스: NotoSansKR OFL 1.1 또는 NanumGothic OFL 1.1 권장\r\n // 참고: spec Section 11 W1, Section 12 V1\r\n\r\n // 현재 stub 상태 — pdf 파라미터는 V1 구현 시 사용됨\r\n void pdf;\r\n\r\n console.warn(\r\n '[grid-export] exportToPdf: fontFamily: \"korean\" 옵션은 stub 상태입니다. ' +\r\n '한국어 글자가 깨질 수 있습니다. ' +\r\n 'loadKoreanFont.ts V1 구현 완료 후 사용하세요. (spec Section 12 V1)',\r\n );\r\n}\r\n","import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import type { Table } from '@tanstack/react-table';\nimport * as XLSX from 'xlsx';\n\n/**\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\n */\nfunction resolveHeaderText(headerValue: unknown): string {\n if (typeof headerValue === 'string') {\n return headerValue;\n }\n return '';\n}\n\n/**\n * TanStack Table 의 헤더 그룹을 순회하여\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\n * 을 반환한다.\n *\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\n *\n * MOD-GRID-25 G-1: `exportToExcel` 의 private helper 에서 `internal/` 로 이동(동작 동일) —\n * `exportSheetsToExcel`(다중 시트) 와 공유하기 위함. 로직 변경 없음.\n */\nexport function buildHeaderRows<TData>(table: Table<TData>): {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n} {\n const headerGroups = table.getHeaderGroups();\n const merges: XLSX.Range[] = [];\n const headerRows: unknown[][] = [];\n\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\n const group = headerGroups[rowIdx];\n const row: unknown[] = [];\n\n let colIdx = 0;\n for (const header of group.headers) {\n if (header.isPlaceholder) {\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\n row.push('');\n } else {\n const text = resolveHeaderText(\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id,\n );\n row.push(text);\n\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\n if (header.colSpan > 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\n });\n }\n }\n colIdx++;\n }\n\n headerRows.push(row);\n }\n\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\n if (headerGroups.length > 1) {\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\n const group = headerGroups[rowIdx];\n let colIdx = 0;\n for (const header of group.headers) {\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\n if (!header.isPlaceholder && header.colSpan === 1) {\n if (rowIdx + 1 <= headerGroups.length - 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: headerGroups.length - 1, c: colIdx },\n });\n }\n }\n colIdx++;\n }\n }\n }\n\n return { headerRows, merges };\n}\n","import * as XLSX from 'xlsx';\n\n/**\n * 헤더 행 + 데이터 행 + (선택) 네이티브 숫자서식·컬럼 폭 → xlsx `WorkSheet` 빌더.\n *\n * MOD-GRID-25 G-1: `exportToExcel`(단일 시트) 와 `exportSheetsToExcel`(다중 시트) 가 공유.\n * **writeFile 과 분리** — 순수하게 `WorkSheet` 만 반환하므로 node 라운드트립 검증 가능\n * (download 부작용 없음). aoa_to_sheet + merges 동작은 기존 `exportToExcel` 과 동일.\n *\n * 숫자서식: `columnFormats[colId]` 가 있으면 해당 컬럼 **데이터 셀**에 네이티브 Excel\n * number-format(`.z`)을 세팅한다. numeric 셀(`aoa_to_sheet` 가 `t:'n'` 추론)은 type 보존 —\n * `toLocaleString` 문자열 강제와 달리 Excel 안에서 numeric·정렬가능하게 유지된다.\n *\n * 폰트/배경(`.s`) 은 **의도적으로 적용하지 않는다** — community `xlsx@0.18.5` 가 write 시\n * 스트립하므로(round-trip 실측: `.s` → `{patternType:'none'}`) no-op 을 동작처럼 ship 하지\n * 않는다. README 에 한계 명시.\n */\nexport function buildGridWorksheet(params: {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n dataRows: unknown[][];\n /** 데이터 컬럼 순서와 1:1 대응하는 leaf 컬럼 id 배열 (format/width 매핑 키) */\n leafColumnIds: string[];\n /** columnId → Excel number-format 코드 (예 '#,##0.00', 'yyyy-mm-dd') */\n columnFormats?: Record<string, string> | undefined;\n /** columnId → 컬럼 폭 (xlsx wch 단위) */\n columnWidths?: Record<string, number> | undefined;\n}): XLSX.WorkSheet {\n const {\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n } = params;\n\n const aoa: unknown[][] = [...headerRows, ...dataRows];\n const ws = XLSX.utils.aoa_to_sheet(aoa);\n\n if (merges.length > 0) {\n ws['!merges'] = merges;\n }\n\n // 네이티브 숫자서식(.z) — 데이터 셀에만 (헤더 행 offset 만큼 아래)\n if (columnFormats) {\n const headerOffset = headerRows.length;\n for (let c = 0; c < leafColumnIds.length; c++) {\n const fmt = columnFormats[leafColumnIds[c]];\n if (!fmt) continue;\n for (let r = 0; r < dataRows.length; r++) {\n const addr = XLSX.utils.encode_cell({ r: headerOffset + r, c });\n const cell = ws[addr];\n // numeric 셀에만 적용 — Excel number-format 은 numeric 에서만 유효.\n // string 셀에 `.z` 를 달면 화면 변화 없는 silent no-op([[LESS-004]] 와 동형)이라 건너뛴다.\n // JS Date 는 aoa_to_sheet 가 numeric serial(`t:'n'`)로 변환하므로 날짜서식도 여기서 적용됨.\n if (cell && cell.t === 'n') {\n cell.z = fmt;\n }\n }\n }\n }\n\n // 컬럼 폭 → !cols (지정된 컬럼만; 미지정은 기본 폭 유지)\n if (columnWidths) {\n const hasAny = leafColumnIds.some(\n (id) => columnWidths[id] !== undefined,\n );\n if (hasAny) {\n ws['!cols'] = leafColumnIds.map((id) =>\n columnWidths[id] !== undefined ? { wch: columnWidths[id] } : {},\n );\n }\n }\n\n return ws;\n}\n","import * as XLSX from 'xlsx';\nimport type { Table } from '@tanstack/react-table';\nimport type { ExcelExportOptions } from './types';\nimport { getRowsByScope } from './internal/getRowsByScope';\nimport { buildHeaderRows } from './internal/buildHeaderRows';\nimport { buildGridWorksheet } from './internal/buildGridWorksheet';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\n *\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior, columnFormats, columnWidths)\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\n *\n * @remarks\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\n *\n * **셀 서식(MOD-GRID-25)**: `columnFormats` 는 네이티브 Excel number-format(`.z`)을 적용해\n * 셀이 Excel 안에서 numeric·정렬가능하게 유지됩니다. 폰트/배경색은 community\n * `xlsx@0.18.5` 가 write 시 미지원(round-trip 시 스트립)이라 제공하지 않습니다.\n *\n * @example\n * // 기본 사용 (filtered 행)\n * exportToExcel(table, { fileName: '데이터.xlsx' });\n *\n * @example\n * // 선택 행 + 다중행 헤더\n * exportToExcel(table, {\n * fileName: '선택데이터.xlsx',\n * sheetName: '선택목록',\n * scope: 'selected',\n * emptyBehavior: 'empty',\n * });\n *\n * @example\n * // 네이티브 숫자서식 + 컬럼 폭\n * exportToExcel(table, {\n * columnFormats: { price: '#,##0.00', orderedAt: 'yyyy-mm-dd' },\n * columnWidths: { name: 30 },\n * });\n */\nexport function exportToExcel<TData>(\n table: Table<TData>,\n options?: ExcelExportOptions,\n): void {\n const {\n fileName = 'export.xlsx',\n sheetName = 'Sheet1',\n scope = 'filtered',\n emptyBehavior = 'skip',\n columnFormats,\n columnWidths,\n } = options ?? {};\n\n // 1) 행 결정 (C-2: TanStack 표준 API만)\n const rows = getRowsByScope(table, scope);\n\n // 2) 빈 데이터 처리 (EC-01, EC-03)\n if (rows.length === 0 && emptyBehavior === 'skip') {\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\n return;\n }\n\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\n const { headerRows, merges } = buildHeaderRows(table);\n\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\n const dataRows: unknown[][] = rows.map((row) =>\n row.getVisibleCells().map((cell) => {\n const value = cell.getValue();\n return value !== undefined && value !== null ? value : '';\n }),\n );\n\n // 5) 워크시트 빌드 (AOA + merge + 네이티브 숫자서식/폭, MOD-GRID-25 G-1)\n const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);\n const ws = buildGridWorksheet({\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n });\n\n // 6) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\n const wb = XLSX.utils.book_new();\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\n const finalFileName = fileName.endsWith('.xlsx')\n ? fileName\n : `${fileName}.xlsx`;\n XLSX.writeFile(wb, finalFileName);\n}\n","import type { Table } from '@tanstack/react-table';\r\nimport type { CSVExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helper — RFC 4180 이스케이프\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * RFC 4180 §2: 구분자/큰따옴표/개행 포함 시 큰따옴표 래핑 + 내부 따옴표 이중화\r\n * (외부 라이브러리 0 — 순수 string 조작, AC-001, AC-003)\r\n */\r\nfunction escapeCsvValue(value: string, delimiter: string): string {\r\n const needsQuoting =\r\n value.includes(delimiter) ||\r\n value.includes('\"') ||\r\n value.includes('\\n') ||\r\n value.includes('\\r');\r\n if (!needsQuoting) return value;\r\n return '\"' + value.split('\"').join('\"\"') + '\"';\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 CSV 파일을 생성·다운로드한다.\r\n * UTF-8 BOM() 포함 — 한국어 Excel 정상 표시 (AC-004).\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options CSV export 옵션 (fileName, scope, delimiter, emptyBehavior)\r\n * @returns void (순수 string 조작 + createObjectURL — 외부 라이브러리 0, D5)\r\n *\r\n * @remarks\r\n * **브라우저 전용**: URL.createObjectURL / document.createElement — SSR 환경 불가.\r\n *\r\n * @example\r\n * // 기본 사용 (filtered 행, 쉼표 구분자)\r\n * exportToCSV(table, { fileName: '데이터.csv' });\r\n *\r\n * @example\r\n * // TSV + 선택 행\r\n * exportToCSV(table, { fileName: '선택.tsv', delimiter: '\\t', scope: 'selected' });\r\n */\r\nexport function exportToCSV<TData>(\r\n table: Table<TData>,\r\n options?: CSVExportOptions,\r\n): void {\r\n const {\r\n fileName = 'export.csv',\r\n scope = 'filtered',\r\n delimiter = ',',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-02)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToCSV: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 추출 — 리프 헤더만 (단일행 CSV, CSV는 다중행 헤더 미지원)\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerRow = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return escapeCsvValue(text, delimiter);\r\n })\r\n .join(delimiter);\r\n\r\n // 4) 데이터 행 추출 (EC-04: null/undefined → 빈 문자열)\r\n const dataRows = rows.map((row) =>\r\n row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str =\r\n value !== null && value !== undefined ? String(value) : '';\r\n return escapeCsvValue(str, delimiter);\r\n })\r\n .join(delimiter),\r\n );\r\n\r\n // 5) CSV 문자열 조립 (AC-003 RFC 4180: CRLF 행 구분)\r\n const lines = [headerRow, ...dataRows];\r\n const csvString = lines.join('\\r\\n');\r\n\r\n // 6) BOM + Blob 생성 → 다운로드 (AC-004:  UTF-8 BOM → 한국어 Excel 정상)\r\n const bom = '';\r\n const blob = new Blob([bom + csvString], {\r\n type: 'text/csv;charset=utf-8;',\r\n });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement('a');\r\n link.href = url;\r\n\r\n // 7) fileName 확장자 자동 추가 (EC-05: .csv 또는 .tsv 없으면 .csv 추가)\r\n const finalFileName =\r\n fileName.endsWith('.csv') || fileName.endsWith('.tsv')\r\n ? fileName\r\n : `${fileName}.csv`;\r\n link.download = finalFileName;\r\n link.click();\r\n URL.revokeObjectURL(url);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { PDFExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 PDF 파일을 생성·다운로드한다.\r\n * jspdf + jspdf-autotable을 optional peer로 dynamic import하여 사용.\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options - PDF export 옵션 (fileName, title, scope, orientation, fontFamily, emptyBehavior)\r\n * @returns Promise<void> — jspdf dynamic import 후 완료\r\n * @throws Error jspdf 또는 jspdf-autotable이 설치되지 않은 경우\r\n *\r\n * @remarks\r\n * **peer 설치 필요**: jspdf + jspdf-autotable은 optional peerDependency.\r\n * 사용 전 `npm install jspdf jspdf-autotable` 실행 필요.\r\n *\r\n * **한국어 폰트**: `fontFamily: 'korean'` 옵션은 W1 리스크 (loadKoreanFont.ts stub 상태).\r\n * 실 폰트 base64 데이터 확보 + 라이선스 확인 후 loadKoreanFont.ts 구현 완료 필요.\r\n *\r\n * @example\r\n * // 기본 사용 (portrait, filtered, Helvetica)\r\n * await exportToPdf(table, { fileName: '보고서.pdf' });\r\n *\r\n * @example\r\n * // 가로 방향 + 전체 행 + 제목\r\n * await exportToPdf(table, {\r\n * fileName: '전체데이터.pdf',\r\n * title: '2026년 데이터 목록',\r\n * scope: 'all',\r\n * orientation: 'l',\r\n * emptyBehavior: 'empty',\r\n * });\r\n */\r\nexport async function exportToPdf<TData>(\r\n table: Table<TData>,\r\n options?: PDFExportOptions,\r\n): Promise<void> {\r\n const {\r\n fileName = 'export.pdf',\r\n title,\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n orientation = 'p',\r\n fontFamily = 'default',\r\n } = options ?? {};\r\n\r\n // 1. jspdf dynamic import (optional peer — 미설치 시 명확한 Error)\r\n let jsPDF: Awaited<typeof import('jspdf')>['default'];\r\n try {\r\n const mod = await import('jspdf');\r\n jsPDF = mod.default;\r\n } catch {\r\n throw new Error(\r\n '[exportToPdf] jspdf is not installed. Run: npm install jspdf jspdf-autotable',\r\n );\r\n }\r\n\r\n // 2. jspdf-autotable dynamic import (optional peer — 미설치 시 명확한 Error)\r\n try {\r\n await import('jspdf-autotable');\r\n } catch {\r\n throw new Error(\r\n '[exportToPdf] jspdf-autotable is not installed. Run: npm install jspdf jspdf-autotable',\r\n );\r\n }\r\n\r\n // 3. 행 수집 (getRowsByScope 재사용 — G-002 추출 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToPdf: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 4. 다중행 헤더 구성 (AC-005: isPlaceholder + colSpan 처리)\r\n const headerGroups = table.getHeaderGroups();\r\n const head: string[][] = headerGroups.map((hg) =>\r\n hg.headers.map((h) => {\r\n if (h.isPlaceholder) return '';\r\n return typeof h.column.columnDef.header === 'string'\r\n ? h.column.columnDef.header\r\n : h.column.id;\r\n }),\r\n );\r\n\r\n // 5. 데이터 행 구성 (리프 헤더 순서 기준 — D7)\r\n const leafHeaders = table.getLeafHeaders();\r\n const body: string[][] = rows.map((row) => {\r\n const cells = row.getVisibleCells();\r\n return leafHeaders.map((lh) => {\r\n const cell = cells.find((c) => c.column.id === lh.column.id);\r\n return cell ? String(cell.getValue() ?? '') : '';\r\n });\r\n });\r\n\r\n // 6. jsPDF 인스턴스 생성\r\n const doc = new jsPDF({ orientation, unit: 'pt', format: 'a4' });\r\n\r\n // 7. 한국어 폰트 로드 (fontFamily === 'korean' 시 dynamic import)\r\n if (fontFamily === 'korean') {\r\n const { loadKoreanFont } = await import('./internal/loadKoreanFont');\r\n await loadKoreanFont(doc);\r\n }\r\n\r\n // 8. title 행 (있으면)\r\n if (title) {\r\n doc.text(title, 14, 20);\r\n }\r\n\r\n // 9. autoTable 렌더링\r\n // @ts-expect-error jspdf-autotable extends jsPDF prototype at runtime (W2 — see spec Section 11)\r\n doc.autoTable({ head, body, startY: title ? 30 : 14 });\r\n\r\n // 10. 파일명 정규화 + 다운로드\r\n const normalized = fileName.endsWith('.pdf') ? fileName : `${fileName}.pdf`;\r\n doc.save(normalized);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { ClipboardOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helper — TSV 이스케이프\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TSV 셀 값 이스케이프: 탭/개행/캐리지리턴 → 공백 치환 (D5)\r\n * TSV는 RFC 표준 quoting 없음 — delimiter(탭) 포함 값은 공백으로 대체가 Excel 호환 최우선 전략.\r\n */\r\nfunction escapeTsvValue(value: string): string {\r\n return value.replace(/[\\t\\r\\n]/g, ' ');\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 데이터를 TSV 포맷으로 클립보드에 복사한다.\r\n * TSV(탭 구분, 줄바꿈 행 구분) — Excel 붙여넣기 호환.\r\n *\r\n * navigator.clipboard 미지원 환경: document.execCommand('copy') fallback 시도.\r\n * fallback도 실패 시 Error('[grid-export] copyToClipboard: Clipboard API not supported') throw.\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options 클립보드 복사 옵션 (scope, emptyBehavior)\r\n * @returns Promise<void> — navigator.clipboard.writeText 는 async\r\n *\r\n * @example\r\n * await copyToClipboard(table);\r\n *\r\n * @example\r\n * await copyToClipboard(table, { scope: 'selected' });\r\n */\r\nexport async function copyToClipboard<TData>(\r\n table: Table<TData>,\r\n options?: ClipboardOptions,\r\n): Promise<void> {\r\n const {\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n includeHeader = true,\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] copyToClipboard: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 행 구성 — 리프 헤더만\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerRow = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return escapeTsvValue(text);\r\n })\r\n .join('\\t');\r\n\r\n // 4) 데이터 행 구성 (EC-07: null/undefined → 빈 문자열, EC-01/EC-02: 탭/개행 → 공백)\r\n const dataRows = rows.map((row) =>\r\n row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str = value !== null && value !== undefined ? String(value) : '';\r\n return escapeTsvValue(str);\r\n })\r\n .join('\\t'),\r\n );\r\n\r\n // 5) TSV 문자열 조립 (includeHeader 시 헤더 + 데이터, 아니면 데이터만 — 줄바꿈 행 구분)\r\n const tsvString = (includeHeader ? [headerRow, ...dataRows] : dataRows).join(\r\n '\\n',\r\n );\r\n\r\n // 6) 클립보드 쓰기 (navigator.clipboard 우선, execCommand fallback — D6, EC-04)\r\n if (\r\n typeof navigator !== 'undefined' &&\r\n navigator.clipboard &&\r\n typeof navigator.clipboard.writeText === 'function'\r\n ) {\r\n await navigator.clipboard.writeText(tsvString);\r\n } else {\r\n // fallback: document.execCommand('copy') — HTTP 개발 환경 또는 구형 브라우저 (EC-04)\r\n const textarea = document.createElement('textarea');\r\n textarea.value = tsvString;\r\n textarea.style.position = 'fixed';\r\n textarea.style.opacity = '0';\r\n document.body.appendChild(textarea);\r\n textarea.select();\r\n const success = document.execCommand('copy');\r\n document.body.removeChild(textarea);\r\n if (!success) {\r\n throw new Error('[grid-export] copyToClipboard: Clipboard API not supported');\r\n }\r\n }\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { PrintOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n/**\r\n * TanStack Table 데이터를 새 팝업 창에 HTML 테이블로 렌더링하여 인쇄 대화상자를 연다.\r\n * 순수 Web API 전용 (window.open + document.write + window.print).\r\n *\r\n * 팝업 차단 환경: console.warn 후 즉시 반환 (D7 — throw 하지 않음).\r\n * printGrid 자체는 동기 반환 (void). 실제 print 발화는 popup.onload 내에서 비동기 실행 (D4).\r\n *\r\n * @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options 인쇄 옵션 (title, scope, orientation, emptyBehavior)\r\n * @returns void\r\n *\r\n * @example\r\n * printGrid(table);\r\n *\r\n * @example\r\n * printGrid(table, { title: '계약 목록', scope: 'filtered', orientation: 'l' });\r\n */\r\nexport function printGrid<TData>(\r\n table: Table<TData>,\r\n options?: PrintOptions,\r\n): void {\r\n const {\r\n title,\r\n scope = 'filtered',\r\n orientation = 'p',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API — getRowsByScope 공유 헬퍼, D1)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] printGrid: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 HTML 구성\r\n const leafHeaders = table.getLeafHeaders();\r\n const headerHtml = leafHeaders\r\n .map((h) => {\r\n const headerDef = h.column.columnDef.header;\r\n const text = typeof headerDef === 'string' ? headerDef : h.column.id;\r\n return `<th>${text}</th>`;\r\n })\r\n .join('');\r\n\r\n // 4) 데이터 행 HTML 구성 (EC-07: null/undefined → 빈 문자열)\r\n const bodyHtml = rows\r\n .map((row) => {\r\n const cells = row\r\n .getVisibleCells()\r\n .map((cell) => {\r\n const value = cell.getValue();\r\n const str = value !== null && value !== undefined ? String(value) : '';\r\n return `<td>${str}</td>`;\r\n })\r\n .join('');\r\n return `<tr>${cells}</tr>`;\r\n })\r\n .join('');\r\n\r\n // 5) HTML 문서 구성\r\n const orientationCss = orientation === 'l' ? 'landscape' : 'portrait';\r\n const titleHtml = title ? `<h2>${title}</h2>` : '';\r\n\r\n const html = `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<title>${title ?? ''}</title>\r\n<style>\r\n @page { size: ${orientationCss}; }\r\n body { font-family: sans-serif; font-size: 12px; margin: 16px; }\r\n h2 { font-size: 16px; margin-bottom: 12px; }\r\n table { border-collapse: collapse; width: 100%; }\r\n th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; page-break-inside: avoid; }\r\n th { background: #f0f0f0; }\r\n @media print {\r\n body { margin: 0; }\r\n }\r\n</style>\r\n</head>\r\n<body>\r\n${titleHtml}\r\n<table>\r\n <thead><tr>${headerHtml}</tr></thead>\r\n <tbody>${bodyHtml}</tbody>\r\n</table>\r\n</body>\r\n</html>`;\r\n\r\n // 6) 팝업 창 열기 (EC-05: null 반환 = 팝업 차단 → D7 warn + return)\r\n const popup = window.open('', '_blank');\r\n if (!popup) {\r\n console.warn(\r\n '[grid-export] printGrid: 팝업이 차단되었습니다. 브라우저 설정에서 팝업을 허용 후 재시도하세요.',\r\n );\r\n return;\r\n }\r\n\r\n // 7) EC-06: onload 등록을 document.write **이전**에 해야 함 (Firefox/Safari 호환)\r\n // about:blank의 load 이벤트는 document.write 시점에 이미 발화될 수 있으므로\r\n // write 이후 등록 시 핸들러가 실행되지 않을 위험이 있음.\r\n popup.onload = () => {\r\n popup.print();\r\n popup.close();\r\n };\r\n\r\n popup.document.write(html);\r\n popup.document.close();\r\n}\r\n","/**\r\n * exportRowsToExcel — 행 배열 기반 Excel export (ADR-005, E-1 + F-1)\r\n *\r\n * TanStack Table 인스턴스 없이 raw row array + ExcelColumn[] 로 .xlsx 생성.\r\n * 거동 패리티 (spec §3 B1/B2/B3):\r\n * B1 — 컬럼 width: ws['!cols'] 적용 (ExcelColumn.width ?? 15)\r\n * B2 — 헤더 styling: bold + 회색 fill (F3F4F6) — note: xlsx community edition has limited style support\r\n * B3 — formatValue: date/datetime/number/currency 포맷 (ko-KR locale)\r\n *\r\n * @see ExcelColumn\r\n * @see ExportRowsOptions\r\n */\r\nimport * as XLSX from 'xlsx';\r\nimport type { ExcelColumn, ExportRowsOptions } from './types';\r\n\r\n// ── B3: 셀 값 포맷터 ──────────────────────────────────────────────────────\r\n\r\nfunction formatValue(value: unknown, format?: ExcelColumn['format']): unknown {\r\n if (value == null) return '';\r\n if (format === 'date' || format === 'datetime') {\r\n const d = new Date(value as string);\r\n if (isNaN(d.getTime())) return String(value);\r\n return format === 'datetime'\r\n ? d.toLocaleString('ko-KR')\r\n : d.toLocaleDateString('ko-KR');\r\n }\r\n if (format === 'number' || format === 'currency') {\r\n const n = Number(value);\r\n return isNaN(n) ? value : n;\r\n }\r\n return value;\r\n}\r\n\r\n// ── 공개 API ──────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * 행 배열을 Excel 파일(.xlsx)로 다운로드한다.\r\n *\r\n * TanStack `Table<TData>` 인스턴스 없이 사용 가능.\r\n * `@topgrid/grid-export` 의 `exportToExcel(table, options)` 와 평행 지원 (ADR-005 옵션 A).\r\n *\r\n * @example\r\n * exportRowsToExcel(rows, columns, { fileName: '보고서_2026.xlsx' });\r\n *\r\n * @param rows 내보낼 데이터 행 배열\r\n * @param columns 컬럼 정의 배열 (key / header / width? / format?)\r\n * @param options 파일명·시트명·emptyBehavior 옵션\r\n */\r\nexport function exportRowsToExcel<TData extends Record<string, unknown>>(\r\n rows: TData[],\r\n columns: ExcelColumn[],\r\n options?: ExportRowsOptions,\r\n): void {\r\n const {\r\n fileName = 'export.xlsx',\r\n sheetName = 'Sheet1',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[exportRowsToExcel] rows is empty — skipping file creation (emptyBehavior: \"skip\")');\r\n return;\r\n }\r\n\r\n // ── 헤더 + 데이터 AOA 구성 ─────────────────────────────────────────────\r\n const header = columns.map((c) => c.header);\r\n const dataRows = rows.map((row) =>\r\n columns.map((col) => formatValue(row[col.key], col.format))\r\n );\r\n const aoa = rows.length === 0 ? [header] : [header, ...dataRows];\r\n\r\n const ws = XLSX.utils.aoa_to_sheet(aoa);\r\n\r\n // ── B1: 컬럼 width ─────────────────────────────────────────────────────\r\n ws['!cols'] = columns.map((c) => ({ wch: c.width ?? 15 }));\r\n\r\n // ── B2: 헤더 행 styling (굵게 + 회색 fill) ────────────────────────────\r\n // note: xlsx community edition has limited style support\r\n const headerRange = XLSX.utils.decode_range(ws['!ref'] ?? 'A1');\r\n for (let c = headerRange.s.c; c <= headerRange.e.c; c++) {\r\n const cellAddr = XLSX.utils.encode_cell({ r: 0, c });\r\n if (!ws[cellAddr]) continue;\r\n ws[cellAddr].s = { font: { bold: true }, fill: { fgColor: { rgb: 'F3F4F6' } } };\r\n }\r\n\r\n // ── 워크북 생성 + 다운로드 ─────────────────────────────────────────────\r\n const wb = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\r\n\r\n const ext = fileName.endsWith('.xlsx') ? fileName : `${fileName}.xlsx`;\r\n XLSX.writeFile(wb, ext);\r\n}\r\n","import * as XLSX from 'xlsx';\nimport type { ExcelSheet, MultiSheetOptions } from './types';\nimport { getRowsByScope } from './internal/getRowsByScope';\nimport { buildHeaderRows } from './internal/buildHeaderRows';\nimport { buildGridWorksheet } from './internal/buildGridWorksheet';\n\n/**\n * 여러 TanStack Table 을 **하나의 Excel 워크북**(여러 시트)으로 export·다운로드한다.\n * (MOD-GRID-25 G-2 — AG Grid/DevExpress 다중 시트 export 격차 해소)\n *\n * 각 시트는 `exportToExcel`(단일 시트)과 동일한 빌더(`buildGridWorksheet`)를 재사용하므로\n * 헤더 merge·scope·네이티브 숫자서식(`.z`)·컬럼 폭 동작이 단일 시트와 일관된다.\n *\n * @param sheets 시트 정의 배열 (`{ name, table, scope?, columnFormats?, columnWidths? }`)\n * @param options 파일명 옵션\n * @returns void (동기 — xlsx.writeFile)\n *\n * @example\n * exportSheetsToExcel(\n * [\n * { name: '주문', table: ordersTable, columnFormats: { total: '#,##0' } },\n * { name: '고객', table: customersTable, scope: 'selected' },\n * ],\n * { fileName: '월간보고.xlsx' },\n * );\n */\nexport function exportSheetsToExcel(\n sheets: ExcelSheet[],\n options?: MultiSheetOptions,\n): void {\n const { fileName = 'export.xlsx' } = options ?? {};\n\n if (sheets.length === 0) {\n console.warn('[grid-export] exportSheetsToExcel: 시트가 없습니다.');\n return;\n }\n\n const wb = XLSX.utils.book_new();\n\n for (const sheet of sheets) {\n const { name, table, scope = 'filtered', columnFormats, columnWidths } =\n sheet;\n\n const rows = getRowsByScope(table, scope);\n const { headerRows, merges } = buildHeaderRows(table);\n const dataRows: unknown[][] = rows.map((row) =>\n row.getVisibleCells().map((cell) => {\n const value = cell.getValue();\n return value !== undefined && value !== null ? value : '';\n }),\n );\n const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);\n\n const ws = buildGridWorksheet({\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n });\n\n XLSX.utils.book_append_sheet(wb, ws, name);\n }\n\n const finalFileName = fileName.endsWith('.xlsx') ? fileName : `${fileName}.xlsx`;\n XLSX.writeFile(wb, finalFileName);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var XLSX = require('xlsx');
3
+ var XLSX3 = require('xlsx');
4
4
 
5
5
  function _interopNamespace(e) {
6
6
  if (e && e.__esModule) return e;
@@ -20,7 +20,7 @@ function _interopNamespace(e) {
20
20
  return Object.freeze(n);
21
21
  }
22
22
 
23
- var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
23
+ var XLSX3__namespace = /*#__PURE__*/_interopNamespace(XLSX3);
24
24
 
25
25
  // src/exportToExcel.ts
26
26
 
@@ -34,8 +34,6 @@ function getRowsByScope(table, scope) {
34
34
  }
35
35
  return table.getFilteredRowModel().rows;
36
36
  }
37
-
38
- // src/exportToExcel.ts
39
37
  function resolveHeaderText(headerValue) {
40
38
  if (typeof headerValue === "string") {
41
39
  return headerValue;
@@ -88,12 +86,56 @@ function buildHeaderRows(table) {
88
86
  }
89
87
  return { headerRows, merges };
90
88
  }
89
+ function buildGridWorksheet(params) {
90
+ const {
91
+ headerRows,
92
+ merges,
93
+ dataRows,
94
+ leafColumnIds,
95
+ columnFormats,
96
+ columnWidths
97
+ } = params;
98
+ const aoa = [...headerRows, ...dataRows];
99
+ const ws = XLSX3__namespace.utils.aoa_to_sheet(aoa);
100
+ if (merges.length > 0) {
101
+ ws["!merges"] = merges;
102
+ }
103
+ if (columnFormats) {
104
+ const headerOffset = headerRows.length;
105
+ for (let c = 0; c < leafColumnIds.length; c++) {
106
+ const fmt = columnFormats[leafColumnIds[c]];
107
+ if (!fmt) continue;
108
+ for (let r = 0; r < dataRows.length; r++) {
109
+ const addr = XLSX3__namespace.utils.encode_cell({ r: headerOffset + r, c });
110
+ const cell = ws[addr];
111
+ if (cell && cell.t === "n") {
112
+ cell.z = fmt;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ if (columnWidths) {
118
+ const hasAny = leafColumnIds.some(
119
+ (id) => columnWidths[id] !== void 0
120
+ );
121
+ if (hasAny) {
122
+ ws["!cols"] = leafColumnIds.map(
123
+ (id) => columnWidths[id] !== void 0 ? { wch: columnWidths[id] } : {}
124
+ );
125
+ }
126
+ }
127
+ return ws;
128
+ }
129
+
130
+ // src/exportToExcel.ts
91
131
  function exportToExcel(table, options) {
92
132
  const {
93
133
  fileName = "export.xlsx",
94
134
  sheetName = "Sheet1",
95
135
  scope = "filtered",
96
- emptyBehavior = "skip"
136
+ emptyBehavior = "skip",
137
+ columnFormats,
138
+ columnWidths
97
139
  } = options ?? {};
98
140
  const rows = getRowsByScope(table, scope);
99
141
  if (rows.length === 0 && emptyBehavior === "skip") {
@@ -107,15 +149,19 @@ function exportToExcel(table, options) {
107
149
  return value !== void 0 && value !== null ? value : "";
108
150
  })
109
151
  );
110
- const aoa = [...headerRows, ...dataRows];
111
- const ws = XLSX__namespace.utils.aoa_to_sheet(aoa);
112
- if (merges.length > 0) {
113
- ws["!merges"] = merges;
114
- }
115
- const wb = XLSX__namespace.utils.book_new();
116
- XLSX__namespace.utils.book_append_sheet(wb, ws, sheetName);
152
+ const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);
153
+ const ws = buildGridWorksheet({
154
+ headerRows,
155
+ merges,
156
+ dataRows,
157
+ leafColumnIds,
158
+ columnFormats,
159
+ columnWidths
160
+ });
161
+ const wb = XLSX3__namespace.utils.book_new();
162
+ XLSX3__namespace.utils.book_append_sheet(wb, ws, sheetName);
117
163
  const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
118
- XLSX__namespace.writeFile(wb, finalFileName);
164
+ XLSX3__namespace.writeFile(wb, finalFileName);
119
165
  }
120
166
 
121
167
  // src/legacy/downloadExcel.ts