@topgrid/grid-export 0.2.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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +408 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +138 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.mjs +381 -0
- package/dist/index.mjs.map +1 -0
- package/dist/legacy/downloadExcel.cjs +128 -0
- package/dist/legacy/downloadExcel.cjs.map +1 -0
- package/dist/legacy/downloadExcel.d.cts +24 -0
- package/dist/legacy/downloadExcel.d.ts +24 -0
- package/dist/legacy/downloadExcel.mjs +106 -0
- package/dist/legacy/downloadExcel.mjs.map +1 -0
- package/dist/types-BCeqH-13.d.cts +231 -0
- package/dist/types-BCeqH-13.d.ts +231 -0
- package/package.json +70 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
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.cjs';
|
|
3
|
+
export { D as DownloadExcelOptions, e as EmptyBehavior, f as ExportScope } from './types-BCeqH-13.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.
|
|
7
|
+
*
|
|
8
|
+
* @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
9
|
+
* @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)
|
|
10
|
+
* @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시
|
|
14
|
+
* xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.
|
|
15
|
+
* 대용량 사용 시 Web Worker 래핑 권장 (EC-05).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // 기본 사용 (filtered 행)
|
|
19
|
+
* exportToExcel(table, { fileName: '데이터.xlsx' });
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // 선택 행 + 다중행 헤더
|
|
23
|
+
* exportToExcel(table, {
|
|
24
|
+
* fileName: '선택데이터.xlsx',
|
|
25
|
+
* sheetName: '선택목록',
|
|
26
|
+
* scope: 'selected',
|
|
27
|
+
* emptyBehavior: 'empty',
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
declare function exportToExcel<TData>(table: Table<TData>, options?: ExcelExportOptions): void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* TanStack Table 인스턴스를 기반으로 CSV 파일을 생성·다운로드한다.
|
|
34
|
+
* UTF-8 BOM() 포함 — 한국어 Excel 정상 표시 (AC-004).
|
|
35
|
+
*
|
|
36
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
37
|
+
* @param options CSV export 옵션 (fileName, scope, delimiter, emptyBehavior)
|
|
38
|
+
* @returns void (순수 string 조작 + createObjectURL — 외부 라이브러리 0, D5)
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* **브라우저 전용**: URL.createObjectURL / document.createElement — SSR 환경 불가.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // 기본 사용 (filtered 행, 쉼표 구분자)
|
|
45
|
+
* exportToCSV(table, { fileName: '데이터.csv' });
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // TSV + 선택 행
|
|
49
|
+
* exportToCSV(table, { fileName: '선택.tsv', delimiter: '\t', scope: 'selected' });
|
|
50
|
+
*/
|
|
51
|
+
declare function exportToCSV<TData>(table: Table<TData>, options?: CSVExportOptions): void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* TanStack Table 인스턴스를 기반으로 PDF 파일을 생성·다운로드한다.
|
|
55
|
+
* jspdf + jspdf-autotable을 optional peer로 dynamic import하여 사용.
|
|
56
|
+
*
|
|
57
|
+
* @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
58
|
+
* @param options - PDF export 옵션 (fileName, title, scope, orientation, fontFamily, emptyBehavior)
|
|
59
|
+
* @returns Promise<void> — jspdf dynamic import 후 완료
|
|
60
|
+
* @throws Error jspdf 또는 jspdf-autotable이 설치되지 않은 경우
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* **peer 설치 필요**: jspdf + jspdf-autotable은 optional peerDependency.
|
|
64
|
+
* 사용 전 `npm install jspdf jspdf-autotable` 실행 필요.
|
|
65
|
+
*
|
|
66
|
+
* **한국어 폰트**: `fontFamily: 'korean'` 옵션은 W1 리스크 (loadKoreanFont.ts stub 상태).
|
|
67
|
+
* 실 폰트 base64 데이터 확보 + 라이선스 확인 후 loadKoreanFont.ts 구현 완료 필요.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // 기본 사용 (portrait, filtered, Helvetica)
|
|
71
|
+
* await exportToPdf(table, { fileName: '보고서.pdf' });
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // 가로 방향 + 전체 행 + 제목
|
|
75
|
+
* await exportToPdf(table, {
|
|
76
|
+
* fileName: '전체데이터.pdf',
|
|
77
|
+
* title: '2026년 데이터 목록',
|
|
78
|
+
* scope: 'all',
|
|
79
|
+
* orientation: 'l',
|
|
80
|
+
* emptyBehavior: 'empty',
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
declare function exportToPdf<TData>(table: Table<TData>, options?: PDFExportOptions): Promise<void>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* TanStack Table 데이터를 TSV 포맷으로 클립보드에 복사한다.
|
|
87
|
+
* TSV(탭 구분, 줄바꿈 행 구분) — Excel 붙여넣기 호환.
|
|
88
|
+
*
|
|
89
|
+
* navigator.clipboard 미지원 환경: document.execCommand('copy') fallback 시도.
|
|
90
|
+
* fallback도 실패 시 Error('[grid-export] copyToClipboard: Clipboard API not supported') throw.
|
|
91
|
+
*
|
|
92
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
93
|
+
* @param options 클립보드 복사 옵션 (scope, emptyBehavior)
|
|
94
|
+
* @returns Promise<void> — navigator.clipboard.writeText 는 async
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* await copyToClipboard(table);
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* await copyToClipboard(table, { scope: 'selected' });
|
|
101
|
+
*/
|
|
102
|
+
declare function copyToClipboard<TData>(table: Table<TData>, options?: ClipboardOptions): Promise<void>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* TanStack Table 데이터를 새 팝업 창에 HTML 테이블로 렌더링하여 인쇄 대화상자를 연다.
|
|
106
|
+
* 순수 Web API 전용 (window.open + document.write + window.print).
|
|
107
|
+
*
|
|
108
|
+
* 팝업 차단 환경: console.warn 후 즉시 반환 (D7 — throw 하지 않음).
|
|
109
|
+
* printGrid 자체는 동기 반환 (void). 실제 print 발화는 popup.onload 내에서 비동기 실행 (D4).
|
|
110
|
+
*
|
|
111
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
112
|
+
* @param options 인쇄 옵션 (title, scope, orientation, emptyBehavior)
|
|
113
|
+
* @returns void
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* printGrid(table);
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* printGrid(table, { title: '계약 목록', scope: 'filtered', orientation: 'l' });
|
|
120
|
+
*/
|
|
121
|
+
declare function printGrid<TData>(table: Table<TData>, options?: PrintOptions): void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 행 배열을 Excel 파일(.xlsx)로 다운로드한다.
|
|
125
|
+
*
|
|
126
|
+
* TanStack `Table<TData>` 인스턴스 없이 사용 가능.
|
|
127
|
+
* `@topgrid/grid-export` 의 `exportToExcel(table, options)` 와 평행 지원 (ADR-005 옵션 A).
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* exportRowsToExcel(rows, columns, { fileName: '보고서_2026.xlsx' });
|
|
131
|
+
*
|
|
132
|
+
* @param rows 내보낼 데이터 행 배열
|
|
133
|
+
* @param columns 컬럼 정의 배열 (key / header / width? / format?)
|
|
134
|
+
* @param options 파일명·시트명·emptyBehavior 옵션
|
|
135
|
+
*/
|
|
136
|
+
declare function exportRowsToExcel<TData extends Record<string, unknown>>(rows: TData[], columns: ExcelColumn[], options?: ExportRowsOptions): void;
|
|
137
|
+
|
|
138
|
+
export { CSVExportOptions, ClipboardOptions, ExcelColumn, ExcelExportOptions, ExportRowsOptions, PDFExportOptions, PrintOptions, copyToClipboard, exportRowsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
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';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.
|
|
7
|
+
*
|
|
8
|
+
* @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
9
|
+
* @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)
|
|
10
|
+
* @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시
|
|
14
|
+
* xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.
|
|
15
|
+
* 대용량 사용 시 Web Worker 래핑 권장 (EC-05).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // 기본 사용 (filtered 행)
|
|
19
|
+
* exportToExcel(table, { fileName: '데이터.xlsx' });
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // 선택 행 + 다중행 헤더
|
|
23
|
+
* exportToExcel(table, {
|
|
24
|
+
* fileName: '선택데이터.xlsx',
|
|
25
|
+
* sheetName: '선택목록',
|
|
26
|
+
* scope: 'selected',
|
|
27
|
+
* emptyBehavior: 'empty',
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
declare function exportToExcel<TData>(table: Table<TData>, options?: ExcelExportOptions): void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* TanStack Table 인스턴스를 기반으로 CSV 파일을 생성·다운로드한다.
|
|
34
|
+
* UTF-8 BOM() 포함 — 한국어 Excel 정상 표시 (AC-004).
|
|
35
|
+
*
|
|
36
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
37
|
+
* @param options CSV export 옵션 (fileName, scope, delimiter, emptyBehavior)
|
|
38
|
+
* @returns void (순수 string 조작 + createObjectURL — 외부 라이브러리 0, D5)
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* **브라우저 전용**: URL.createObjectURL / document.createElement — SSR 환경 불가.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // 기본 사용 (filtered 행, 쉼표 구분자)
|
|
45
|
+
* exportToCSV(table, { fileName: '데이터.csv' });
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // TSV + 선택 행
|
|
49
|
+
* exportToCSV(table, { fileName: '선택.tsv', delimiter: '\t', scope: 'selected' });
|
|
50
|
+
*/
|
|
51
|
+
declare function exportToCSV<TData>(table: Table<TData>, options?: CSVExportOptions): void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* TanStack Table 인스턴스를 기반으로 PDF 파일을 생성·다운로드한다.
|
|
55
|
+
* jspdf + jspdf-autotable을 optional peer로 dynamic import하여 사용.
|
|
56
|
+
*
|
|
57
|
+
* @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
58
|
+
* @param options - PDF export 옵션 (fileName, title, scope, orientation, fontFamily, emptyBehavior)
|
|
59
|
+
* @returns Promise<void> — jspdf dynamic import 후 완료
|
|
60
|
+
* @throws Error jspdf 또는 jspdf-autotable이 설치되지 않은 경우
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* **peer 설치 필요**: jspdf + jspdf-autotable은 optional peerDependency.
|
|
64
|
+
* 사용 전 `npm install jspdf jspdf-autotable` 실행 필요.
|
|
65
|
+
*
|
|
66
|
+
* **한국어 폰트**: `fontFamily: 'korean'` 옵션은 W1 리스크 (loadKoreanFont.ts stub 상태).
|
|
67
|
+
* 실 폰트 base64 데이터 확보 + 라이선스 확인 후 loadKoreanFont.ts 구현 완료 필요.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // 기본 사용 (portrait, filtered, Helvetica)
|
|
71
|
+
* await exportToPdf(table, { fileName: '보고서.pdf' });
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // 가로 방향 + 전체 행 + 제목
|
|
75
|
+
* await exportToPdf(table, {
|
|
76
|
+
* fileName: '전체데이터.pdf',
|
|
77
|
+
* title: '2026년 데이터 목록',
|
|
78
|
+
* scope: 'all',
|
|
79
|
+
* orientation: 'l',
|
|
80
|
+
* emptyBehavior: 'empty',
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
declare function exportToPdf<TData>(table: Table<TData>, options?: PDFExportOptions): Promise<void>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* TanStack Table 데이터를 TSV 포맷으로 클립보드에 복사한다.
|
|
87
|
+
* TSV(탭 구분, 줄바꿈 행 구분) — Excel 붙여넣기 호환.
|
|
88
|
+
*
|
|
89
|
+
* navigator.clipboard 미지원 환경: document.execCommand('copy') fallback 시도.
|
|
90
|
+
* fallback도 실패 시 Error('[grid-export] copyToClipboard: Clipboard API not supported') throw.
|
|
91
|
+
*
|
|
92
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
93
|
+
* @param options 클립보드 복사 옵션 (scope, emptyBehavior)
|
|
94
|
+
* @returns Promise<void> — navigator.clipboard.writeText 는 async
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* await copyToClipboard(table);
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* await copyToClipboard(table, { scope: 'selected' });
|
|
101
|
+
*/
|
|
102
|
+
declare function copyToClipboard<TData>(table: Table<TData>, options?: ClipboardOptions): Promise<void>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* TanStack Table 데이터를 새 팝업 창에 HTML 테이블로 렌더링하여 인쇄 대화상자를 연다.
|
|
106
|
+
* 순수 Web API 전용 (window.open + document.write + window.print).
|
|
107
|
+
*
|
|
108
|
+
* 팝업 차단 환경: console.warn 후 즉시 반환 (D7 — throw 하지 않음).
|
|
109
|
+
* printGrid 자체는 동기 반환 (void). 실제 print 발화는 popup.onload 내에서 비동기 실행 (D4).
|
|
110
|
+
*
|
|
111
|
+
* @param table TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)
|
|
112
|
+
* @param options 인쇄 옵션 (title, scope, orientation, emptyBehavior)
|
|
113
|
+
* @returns void
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* printGrid(table);
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* printGrid(table, { title: '계약 목록', scope: 'filtered', orientation: 'l' });
|
|
120
|
+
*/
|
|
121
|
+
declare function printGrid<TData>(table: Table<TData>, options?: PrintOptions): void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 행 배열을 Excel 파일(.xlsx)로 다운로드한다.
|
|
125
|
+
*
|
|
126
|
+
* TanStack `Table<TData>` 인스턴스 없이 사용 가능.
|
|
127
|
+
* `@topgrid/grid-export` 의 `exportToExcel(table, options)` 와 평행 지원 (ADR-005 옵션 A).
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* exportRowsToExcel(rows, columns, { fileName: '보고서_2026.xlsx' });
|
|
131
|
+
*
|
|
132
|
+
* @param rows 내보낼 데이터 행 배열
|
|
133
|
+
* @param columns 컬럼 정의 배열 (key / header / width? / format?)
|
|
134
|
+
* @param options 파일명·시트명·emptyBehavior 옵션
|
|
135
|
+
*/
|
|
136
|
+
declare function exportRowsToExcel<TData extends Record<string, unknown>>(rows: TData[], columns: ExcelColumn[], options?: ExportRowsOptions): void;
|
|
137
|
+
|
|
138
|
+
export { CSVExportOptions, ClipboardOptions, ExcelColumn, ExcelExportOptions, ExportRowsOptions, PDFExportOptions, PrintOptions, copyToClipboard, exportRowsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import * as XLSX2 from 'xlsx';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/internal/loadKoreanFont.ts
|
|
14
|
+
var loadKoreanFont_exports = {};
|
|
15
|
+
__export(loadKoreanFont_exports, {
|
|
16
|
+
loadKoreanFont: () => loadKoreanFont
|
|
17
|
+
});
|
|
18
|
+
async function loadKoreanFont(pdf) {
|
|
19
|
+
console.warn(
|
|
20
|
+
'[grid-export] exportToPdf: fontFamily: "korean" \uC635\uC158\uC740 stub \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD55C\uAD6D\uC5B4 \uAE00\uC790\uAC00 \uAE68\uC9C8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. loadKoreanFont.ts V1 \uAD6C\uD604 \uC644\uB8CC \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. (spec Section 12 V1)'
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
var init_loadKoreanFont = __esm({
|
|
24
|
+
"src/internal/loadKoreanFont.ts"() {
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/internal/getRowsByScope.ts
|
|
29
|
+
function getRowsByScope(table, scope) {
|
|
30
|
+
if (scope === "all") {
|
|
31
|
+
return table.getCoreRowModel().rows;
|
|
32
|
+
}
|
|
33
|
+
if (scope === "selected") {
|
|
34
|
+
return table.getSelectedRowModel().rows;
|
|
35
|
+
}
|
|
36
|
+
return table.getFilteredRowModel().rows;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/exportToExcel.ts
|
|
40
|
+
function resolveHeaderText(headerValue) {
|
|
41
|
+
if (typeof headerValue === "string") {
|
|
42
|
+
return headerValue;
|
|
43
|
+
}
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
function buildHeaderRows(table) {
|
|
47
|
+
const headerGroups = table.getHeaderGroups();
|
|
48
|
+
const merges = [];
|
|
49
|
+
const headerRows = [];
|
|
50
|
+
for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {
|
|
51
|
+
const group = headerGroups[rowIdx];
|
|
52
|
+
const row = [];
|
|
53
|
+
let colIdx = 0;
|
|
54
|
+
for (const header of group.headers) {
|
|
55
|
+
if (header.isPlaceholder) {
|
|
56
|
+
row.push("");
|
|
57
|
+
} else {
|
|
58
|
+
const text = resolveHeaderText(
|
|
59
|
+
typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id
|
|
60
|
+
);
|
|
61
|
+
row.push(text);
|
|
62
|
+
if (header.colSpan > 1) {
|
|
63
|
+
merges.push({
|
|
64
|
+
s: { r: rowIdx, c: colIdx },
|
|
65
|
+
e: { r: rowIdx, c: colIdx + header.colSpan - 1 }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
colIdx++;
|
|
70
|
+
}
|
|
71
|
+
headerRows.push(row);
|
|
72
|
+
}
|
|
73
|
+
if (headerGroups.length > 1) {
|
|
74
|
+
for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {
|
|
75
|
+
const group = headerGroups[rowIdx];
|
|
76
|
+
let colIdx = 0;
|
|
77
|
+
for (const header of group.headers) {
|
|
78
|
+
if (!header.isPlaceholder && header.colSpan === 1) {
|
|
79
|
+
if (rowIdx + 1 <= headerGroups.length - 1) {
|
|
80
|
+
merges.push({
|
|
81
|
+
s: { r: rowIdx, c: colIdx },
|
|
82
|
+
e: { r: headerGroups.length - 1, c: colIdx }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
colIdx++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { headerRows, merges };
|
|
91
|
+
}
|
|
92
|
+
function exportToExcel(table, options) {
|
|
93
|
+
const {
|
|
94
|
+
fileName = "export.xlsx",
|
|
95
|
+
sheetName = "Sheet1",
|
|
96
|
+
scope = "filtered",
|
|
97
|
+
emptyBehavior = "skip"
|
|
98
|
+
} = options ?? {};
|
|
99
|
+
const rows = getRowsByScope(table, scope);
|
|
100
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
101
|
+
console.warn("[grid-export] exportToExcel: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const { headerRows, merges } = buildHeaderRows(table);
|
|
105
|
+
const dataRows = rows.map(
|
|
106
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
107
|
+
const value = cell.getValue();
|
|
108
|
+
return value !== void 0 && value !== null ? value : "";
|
|
109
|
+
})
|
|
110
|
+
);
|
|
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);
|
|
118
|
+
const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
|
|
119
|
+
XLSX2.writeFile(wb, finalFileName);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/exportToCSV.ts
|
|
123
|
+
function escapeCsvValue(value, delimiter) {
|
|
124
|
+
const needsQuoting = value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r");
|
|
125
|
+
if (!needsQuoting) return value;
|
|
126
|
+
return '"' + value.split('"').join('""') + '"';
|
|
127
|
+
}
|
|
128
|
+
function exportToCSV(table, options) {
|
|
129
|
+
const {
|
|
130
|
+
fileName = "export.csv",
|
|
131
|
+
scope = "filtered",
|
|
132
|
+
delimiter = ",",
|
|
133
|
+
emptyBehavior = "skip"
|
|
134
|
+
} = options ?? {};
|
|
135
|
+
const rows = getRowsByScope(table, scope);
|
|
136
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
137
|
+
console.warn("[grid-export] exportToCSV: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const leafHeaders = table.getLeafHeaders();
|
|
141
|
+
const headerRow = leafHeaders.map((h) => {
|
|
142
|
+
const headerDef = h.column.columnDef.header;
|
|
143
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
144
|
+
return escapeCsvValue(text, delimiter);
|
|
145
|
+
}).join(delimiter);
|
|
146
|
+
const dataRows = rows.map(
|
|
147
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
148
|
+
const value = cell.getValue();
|
|
149
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
150
|
+
return escapeCsvValue(str, delimiter);
|
|
151
|
+
}).join(delimiter)
|
|
152
|
+
);
|
|
153
|
+
const lines = [headerRow, ...dataRows];
|
|
154
|
+
const csvString = lines.join("\r\n");
|
|
155
|
+
const bom = "\uFEFF";
|
|
156
|
+
const blob = new Blob([bom + csvString], {
|
|
157
|
+
type: "text/csv;charset=utf-8;"
|
|
158
|
+
});
|
|
159
|
+
const url = URL.createObjectURL(blob);
|
|
160
|
+
const link = document.createElement("a");
|
|
161
|
+
link.href = url;
|
|
162
|
+
const finalFileName = fileName.endsWith(".csv") || fileName.endsWith(".tsv") ? fileName : `${fileName}.csv`;
|
|
163
|
+
link.download = finalFileName;
|
|
164
|
+
link.click();
|
|
165
|
+
URL.revokeObjectURL(url);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/exportToPdf.ts
|
|
169
|
+
async function exportToPdf(table, options) {
|
|
170
|
+
const {
|
|
171
|
+
fileName = "export.pdf",
|
|
172
|
+
title,
|
|
173
|
+
scope = "filtered",
|
|
174
|
+
emptyBehavior = "skip",
|
|
175
|
+
orientation = "p",
|
|
176
|
+
fontFamily = "default"
|
|
177
|
+
} = options ?? {};
|
|
178
|
+
let jsPDF;
|
|
179
|
+
try {
|
|
180
|
+
const mod = await import('jspdf');
|
|
181
|
+
jsPDF = mod.default;
|
|
182
|
+
} catch {
|
|
183
|
+
throw new Error(
|
|
184
|
+
"[exportToPdf] jspdf is not installed. Run: npm install jspdf jspdf-autotable"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
await import('jspdf-autotable');
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"[exportToPdf] jspdf-autotable is not installed. Run: npm install jspdf jspdf-autotable"
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const rows = getRowsByScope(table, scope);
|
|
195
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
196
|
+
console.warn("[grid-export] exportToPdf: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const headerGroups = table.getHeaderGroups();
|
|
200
|
+
const head = headerGroups.map(
|
|
201
|
+
(hg) => hg.headers.map((h) => {
|
|
202
|
+
if (h.isPlaceholder) return "";
|
|
203
|
+
return typeof h.column.columnDef.header === "string" ? h.column.columnDef.header : h.column.id;
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
const leafHeaders = table.getLeafHeaders();
|
|
207
|
+
const body = rows.map((row) => {
|
|
208
|
+
const cells = row.getVisibleCells();
|
|
209
|
+
return leafHeaders.map((lh) => {
|
|
210
|
+
const cell = cells.find((c) => c.column.id === lh.column.id);
|
|
211
|
+
return cell ? String(cell.getValue() ?? "") : "";
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
const doc = new jsPDF({ orientation, unit: "pt", format: "a4" });
|
|
215
|
+
if (fontFamily === "korean") {
|
|
216
|
+
const { loadKoreanFont: loadKoreanFont2 } = await Promise.resolve().then(() => (init_loadKoreanFont(), loadKoreanFont_exports));
|
|
217
|
+
await loadKoreanFont2(doc);
|
|
218
|
+
}
|
|
219
|
+
if (title) {
|
|
220
|
+
doc.text(title, 14, 20);
|
|
221
|
+
}
|
|
222
|
+
doc.autoTable({ head, body, startY: title ? 30 : 14 });
|
|
223
|
+
const normalized = fileName.endsWith(".pdf") ? fileName : `${fileName}.pdf`;
|
|
224
|
+
doc.save(normalized);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/copyToClipboard.ts
|
|
228
|
+
function escapeTsvValue(value) {
|
|
229
|
+
return value.replace(/[\t\r\n]/g, " ");
|
|
230
|
+
}
|
|
231
|
+
async function copyToClipboard(table, options) {
|
|
232
|
+
const { scope = "filtered", emptyBehavior = "skip" } = options ?? {};
|
|
233
|
+
const rows = getRowsByScope(table, scope);
|
|
234
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
235
|
+
console.warn("[grid-export] copyToClipboard: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const leafHeaders = table.getLeafHeaders();
|
|
239
|
+
const headerRow = leafHeaders.map((h) => {
|
|
240
|
+
const headerDef = h.column.columnDef.header;
|
|
241
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
242
|
+
return escapeTsvValue(text);
|
|
243
|
+
}).join(" ");
|
|
244
|
+
const dataRows = rows.map(
|
|
245
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
246
|
+
const value = cell.getValue();
|
|
247
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
248
|
+
return escapeTsvValue(str);
|
|
249
|
+
}).join(" ")
|
|
250
|
+
);
|
|
251
|
+
const tsvString = [headerRow, ...dataRows].join("\n");
|
|
252
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
253
|
+
await navigator.clipboard.writeText(tsvString);
|
|
254
|
+
} else {
|
|
255
|
+
const textarea = document.createElement("textarea");
|
|
256
|
+
textarea.value = tsvString;
|
|
257
|
+
textarea.style.position = "fixed";
|
|
258
|
+
textarea.style.opacity = "0";
|
|
259
|
+
document.body.appendChild(textarea);
|
|
260
|
+
textarea.select();
|
|
261
|
+
const success = document.execCommand("copy");
|
|
262
|
+
document.body.removeChild(textarea);
|
|
263
|
+
if (!success) {
|
|
264
|
+
throw new Error("[grid-export] copyToClipboard: Clipboard API not supported");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/printGrid.ts
|
|
270
|
+
function printGrid(table, options) {
|
|
271
|
+
const {
|
|
272
|
+
title,
|
|
273
|
+
scope = "filtered",
|
|
274
|
+
orientation = "p",
|
|
275
|
+
emptyBehavior = "skip"
|
|
276
|
+
} = options ?? {};
|
|
277
|
+
const rows = getRowsByScope(table, scope);
|
|
278
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
279
|
+
console.warn("[grid-export] printGrid: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const leafHeaders = table.getLeafHeaders();
|
|
283
|
+
const headerHtml = leafHeaders.map((h) => {
|
|
284
|
+
const headerDef = h.column.columnDef.header;
|
|
285
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
286
|
+
return `<th>${text}</th>`;
|
|
287
|
+
}).join("");
|
|
288
|
+
const bodyHtml = rows.map((row) => {
|
|
289
|
+
const cells = row.getVisibleCells().map((cell) => {
|
|
290
|
+
const value = cell.getValue();
|
|
291
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
292
|
+
return `<td>${str}</td>`;
|
|
293
|
+
}).join("");
|
|
294
|
+
return `<tr>${cells}</tr>`;
|
|
295
|
+
}).join("");
|
|
296
|
+
const orientationCss = orientation === "l" ? "landscape" : "portrait";
|
|
297
|
+
const titleHtml = title ? `<h2>${title}</h2>` : "";
|
|
298
|
+
const html = `<!DOCTYPE html>
|
|
299
|
+
<html>
|
|
300
|
+
<head>
|
|
301
|
+
<meta charset="utf-8">
|
|
302
|
+
<title>${title ?? ""}</title>
|
|
303
|
+
<style>
|
|
304
|
+
@page { size: ${orientationCss}; }
|
|
305
|
+
body { font-family: sans-serif; font-size: 12px; margin: 16px; }
|
|
306
|
+
h2 { font-size: 16px; margin-bottom: 12px; }
|
|
307
|
+
table { border-collapse: collapse; width: 100%; }
|
|
308
|
+
th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; page-break-inside: avoid; }
|
|
309
|
+
th { background: #f0f0f0; }
|
|
310
|
+
@media print {
|
|
311
|
+
body { margin: 0; }
|
|
312
|
+
}
|
|
313
|
+
</style>
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
${titleHtml}
|
|
317
|
+
<table>
|
|
318
|
+
<thead><tr>${headerHtml}</tr></thead>
|
|
319
|
+
<tbody>${bodyHtml}</tbody>
|
|
320
|
+
</table>
|
|
321
|
+
</body>
|
|
322
|
+
</html>`;
|
|
323
|
+
const popup = window.open("", "_blank");
|
|
324
|
+
if (!popup) {
|
|
325
|
+
console.warn(
|
|
326
|
+
"[grid-export] printGrid: \uD31D\uC5C5\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uC124\uC815\uC5D0\uC11C \uD31D\uC5C5\uC744 \uD5C8\uC6A9 \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
popup.onload = () => {
|
|
331
|
+
popup.print();
|
|
332
|
+
popup.close();
|
|
333
|
+
};
|
|
334
|
+
popup.document.write(html);
|
|
335
|
+
popup.document.close();
|
|
336
|
+
}
|
|
337
|
+
function formatValue(value, format) {
|
|
338
|
+
if (value == null) return "";
|
|
339
|
+
if (format === "date" || format === "datetime") {
|
|
340
|
+
const d = new Date(value);
|
|
341
|
+
if (isNaN(d.getTime())) return String(value);
|
|
342
|
+
return format === "datetime" ? d.toLocaleString("ko-KR") : d.toLocaleDateString("ko-KR");
|
|
343
|
+
}
|
|
344
|
+
if (format === "number" || format === "currency") {
|
|
345
|
+
const n = Number(value);
|
|
346
|
+
return isNaN(n) ? value : n;
|
|
347
|
+
}
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
350
|
+
function exportRowsToExcel(rows, columns, options) {
|
|
351
|
+
const {
|
|
352
|
+
fileName = "export.xlsx",
|
|
353
|
+
sheetName = "Sheet1",
|
|
354
|
+
emptyBehavior = "skip"
|
|
355
|
+
} = options ?? {};
|
|
356
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
357
|
+
console.warn('[exportRowsToExcel] rows is empty \u2014 skipping file creation (emptyBehavior: "skip")');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const header = columns.map((c) => c.header);
|
|
361
|
+
const dataRows = rows.map(
|
|
362
|
+
(row) => columns.map((col) => formatValue(row[col.key], col.format))
|
|
363
|
+
);
|
|
364
|
+
const aoa = rows.length === 0 ? [header] : [header, ...dataRows];
|
|
365
|
+
const ws = XLSX2.utils.aoa_to_sheet(aoa);
|
|
366
|
+
ws["!cols"] = columns.map((c) => ({ wch: c.width ?? 15 }));
|
|
367
|
+
const headerRange = XLSX2.utils.decode_range(ws["!ref"] ?? "A1");
|
|
368
|
+
for (let c = headerRange.s.c; c <= headerRange.e.c; c++) {
|
|
369
|
+
const cellAddr = XLSX2.utils.encode_cell({ r: 0, c });
|
|
370
|
+
if (!ws[cellAddr]) continue;
|
|
371
|
+
ws[cellAddr].s = { font: { bold: true }, fill: { fgColor: { rgb: "F3F4F6" } } };
|
|
372
|
+
}
|
|
373
|
+
const wb = XLSX2.utils.book_new();
|
|
374
|
+
XLSX2.utils.book_append_sheet(wb, ws, sheetName);
|
|
375
|
+
const ext = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
|
|
376
|
+
XLSX2.writeFile(wb, ext);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { copyToClipboard, exportRowsToExcel, exportToCSV, exportToExcel, exportToPdf, printGrid };
|
|
380
|
+
//# sourceMappingURL=index.mjs.map
|
|
381
|
+
//# sourceMappingURL=index.mjs.map
|