@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Topvel Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @topgrid/grid-export
|
|
2
|
+
|
|
3
|
+
Excel, PDF, CSV export for grid data
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @topgrid/grid-export
|
|
9
|
+
# or
|
|
10
|
+
npm install @topgrid/grid-export
|
|
11
|
+
# or
|
|
12
|
+
yarn add @topgrid/grid-export
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Peer Dependencies
|
|
16
|
+
|
|
17
|
+
| Package | Version | Required |
|
|
18
|
+
|---------|---------|---------|
|
|
19
|
+
| `@tanstack/react-table` | `^8.0.0` | Yes |
|
|
20
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` | Yes |
|
|
21
|
+
| `react-dom` | `^18.0.0 \|\| ^19.0.0` | Yes |
|
|
22
|
+
| `xlsx` | `^0.18.5` | Yes |
|
|
23
|
+
| `jspdf` | `^2.5.0` | Optional (PDF only) |
|
|
24
|
+
| `jspdf-autotable` | `^3.5.0` | Optional (PDF only) |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Excel Export (TanStack Table)
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { exportToExcel } from '@topgrid/grid-export';
|
|
32
|
+
|
|
33
|
+
// Export all visible rows to Excel
|
|
34
|
+
exportToExcel(table, {
|
|
35
|
+
fileName: 'my-data.xlsx',
|
|
36
|
+
sheetName: 'Sheet1',
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Excel Export (Row Array — ADR-005)
|
|
41
|
+
|
|
42
|
+
Use when you have raw row data without a TanStack `Table` instance.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { exportRowsToExcel, type ExcelColumn } from '@topgrid/grid-export';
|
|
46
|
+
|
|
47
|
+
const columns: ExcelColumn[] = [
|
|
48
|
+
{ key: 'name', header: '이름', width: 20 },
|
|
49
|
+
{ key: 'score', header: '점수', width: 10, format: 'number' },
|
|
50
|
+
{ key: 'date', header: '날짜', width: 15, format: 'date' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
exportRowsToExcel(rows, columns, {
|
|
54
|
+
fileName: '보고서_2026.xlsx',
|
|
55
|
+
sheetName: 'Sheet1',
|
|
56
|
+
// emptyBehavior: 'empty' — emit header-only file when rows is empty (default: 'skip')
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### CSV Export
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { exportToCSV } from '@topgrid/grid-export';
|
|
64
|
+
|
|
65
|
+
exportToCSV(table, {
|
|
66
|
+
fileName: 'my-data.csv',
|
|
67
|
+
delimiter: ',',
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### PDF Export
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { exportToPdf } from '@topgrid/grid-export';
|
|
75
|
+
|
|
76
|
+
exportToPdf(table, {
|
|
77
|
+
fileName: 'my-data.pdf',
|
|
78
|
+
title: 'My Grid Report',
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Clipboard & Print
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { copyToClipboard, printGrid } from '@topgrid/grid-export';
|
|
86
|
+
|
|
87
|
+
// Copy selected data to clipboard
|
|
88
|
+
copyToClipboard(table);
|
|
89
|
+
|
|
90
|
+
// Print the grid
|
|
91
|
+
printGrid(table, { title: 'Report' });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Main API
|
|
95
|
+
|
|
96
|
+
| Export | Description |
|
|
97
|
+
|--------|-------------|
|
|
98
|
+
| `exportToExcel` | Export grid data to Excel (.xlsx) — TanStack `Table<TData>` based |
|
|
99
|
+
| `exportRowsToExcel` | Export row array to Excel (.xlsx) — raw `TData[]` + `ExcelColumn[]` based (ADR-005) |
|
|
100
|
+
| `exportToCSV` | Export grid data to CSV |
|
|
101
|
+
| `exportToPdf` | Export grid data to PDF |
|
|
102
|
+
| `copyToClipboard` | Copy grid data to clipboard |
|
|
103
|
+
| `printGrid` | Print grid content |
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
[Documentation](https://grid.tomis.dev) | [API Reference](https://grid.tomis.dev/api)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var XLSX2 = require('xlsx');
|
|
4
|
+
|
|
5
|
+
function _interopNamespace(e) {
|
|
6
|
+
if (e && e.__esModule) return e;
|
|
7
|
+
var n = Object.create(null);
|
|
8
|
+
if (e) {
|
|
9
|
+
Object.keys(e).forEach(function (k) {
|
|
10
|
+
if (k !== 'default') {
|
|
11
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return e[k]; }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var XLSX2__namespace = /*#__PURE__*/_interopNamespace(XLSX2);
|
|
24
|
+
|
|
25
|
+
var __defProp = Object.defineProperty;
|
|
26
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
27
|
+
var __esm = (fn, res) => function __init() {
|
|
28
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
29
|
+
};
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/internal/loadKoreanFont.ts
|
|
36
|
+
var loadKoreanFont_exports = {};
|
|
37
|
+
__export(loadKoreanFont_exports, {
|
|
38
|
+
loadKoreanFont: () => loadKoreanFont
|
|
39
|
+
});
|
|
40
|
+
async function loadKoreanFont(pdf) {
|
|
41
|
+
console.warn(
|
|
42
|
+
'[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)'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
var init_loadKoreanFont = __esm({
|
|
46
|
+
"src/internal/loadKoreanFont.ts"() {
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// src/internal/getRowsByScope.ts
|
|
51
|
+
function getRowsByScope(table, scope) {
|
|
52
|
+
if (scope === "all") {
|
|
53
|
+
return table.getCoreRowModel().rows;
|
|
54
|
+
}
|
|
55
|
+
if (scope === "selected") {
|
|
56
|
+
return table.getSelectedRowModel().rows;
|
|
57
|
+
}
|
|
58
|
+
return table.getFilteredRowModel().rows;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/exportToExcel.ts
|
|
62
|
+
function resolveHeaderText(headerValue) {
|
|
63
|
+
if (typeof headerValue === "string") {
|
|
64
|
+
return headerValue;
|
|
65
|
+
}
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
function buildHeaderRows(table) {
|
|
69
|
+
const headerGroups = table.getHeaderGroups();
|
|
70
|
+
const merges = [];
|
|
71
|
+
const headerRows = [];
|
|
72
|
+
for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {
|
|
73
|
+
const group = headerGroups[rowIdx];
|
|
74
|
+
const row = [];
|
|
75
|
+
let colIdx = 0;
|
|
76
|
+
for (const header of group.headers) {
|
|
77
|
+
if (header.isPlaceholder) {
|
|
78
|
+
row.push("");
|
|
79
|
+
} else {
|
|
80
|
+
const text = resolveHeaderText(
|
|
81
|
+
typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id
|
|
82
|
+
);
|
|
83
|
+
row.push(text);
|
|
84
|
+
if (header.colSpan > 1) {
|
|
85
|
+
merges.push({
|
|
86
|
+
s: { r: rowIdx, c: colIdx },
|
|
87
|
+
e: { r: rowIdx, c: colIdx + header.colSpan - 1 }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
colIdx++;
|
|
92
|
+
}
|
|
93
|
+
headerRows.push(row);
|
|
94
|
+
}
|
|
95
|
+
if (headerGroups.length > 1) {
|
|
96
|
+
for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {
|
|
97
|
+
const group = headerGroups[rowIdx];
|
|
98
|
+
let colIdx = 0;
|
|
99
|
+
for (const header of group.headers) {
|
|
100
|
+
if (!header.isPlaceholder && header.colSpan === 1) {
|
|
101
|
+
if (rowIdx + 1 <= headerGroups.length - 1) {
|
|
102
|
+
merges.push({
|
|
103
|
+
s: { r: rowIdx, c: colIdx },
|
|
104
|
+
e: { r: headerGroups.length - 1, c: colIdx }
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
colIdx++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { headerRows, merges };
|
|
113
|
+
}
|
|
114
|
+
function exportToExcel(table, options) {
|
|
115
|
+
const {
|
|
116
|
+
fileName = "export.xlsx",
|
|
117
|
+
sheetName = "Sheet1",
|
|
118
|
+
scope = "filtered",
|
|
119
|
+
emptyBehavior = "skip"
|
|
120
|
+
} = options ?? {};
|
|
121
|
+
const rows = getRowsByScope(table, scope);
|
|
122
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
123
|
+
console.warn("[grid-export] exportToExcel: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const { headerRows, merges } = buildHeaderRows(table);
|
|
127
|
+
const dataRows = rows.map(
|
|
128
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
129
|
+
const value = cell.getValue();
|
|
130
|
+
return value !== void 0 && value !== null ? value : "";
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
const aoa = [...headerRows, ...dataRows];
|
|
134
|
+
const ws = XLSX2__namespace.utils.aoa_to_sheet(aoa);
|
|
135
|
+
if (merges.length > 0) {
|
|
136
|
+
ws["!merges"] = merges;
|
|
137
|
+
}
|
|
138
|
+
const wb = XLSX2__namespace.utils.book_new();
|
|
139
|
+
XLSX2__namespace.utils.book_append_sheet(wb, ws, sheetName);
|
|
140
|
+
const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
|
|
141
|
+
XLSX2__namespace.writeFile(wb, finalFileName);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/exportToCSV.ts
|
|
145
|
+
function escapeCsvValue(value, delimiter) {
|
|
146
|
+
const needsQuoting = value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r");
|
|
147
|
+
if (!needsQuoting) return value;
|
|
148
|
+
return '"' + value.split('"').join('""') + '"';
|
|
149
|
+
}
|
|
150
|
+
function exportToCSV(table, options) {
|
|
151
|
+
const {
|
|
152
|
+
fileName = "export.csv",
|
|
153
|
+
scope = "filtered",
|
|
154
|
+
delimiter = ",",
|
|
155
|
+
emptyBehavior = "skip"
|
|
156
|
+
} = options ?? {};
|
|
157
|
+
const rows = getRowsByScope(table, scope);
|
|
158
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
159
|
+
console.warn("[grid-export] exportToCSV: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const leafHeaders = table.getLeafHeaders();
|
|
163
|
+
const headerRow = leafHeaders.map((h) => {
|
|
164
|
+
const headerDef = h.column.columnDef.header;
|
|
165
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
166
|
+
return escapeCsvValue(text, delimiter);
|
|
167
|
+
}).join(delimiter);
|
|
168
|
+
const dataRows = rows.map(
|
|
169
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
170
|
+
const value = cell.getValue();
|
|
171
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
172
|
+
return escapeCsvValue(str, delimiter);
|
|
173
|
+
}).join(delimiter)
|
|
174
|
+
);
|
|
175
|
+
const lines = [headerRow, ...dataRows];
|
|
176
|
+
const csvString = lines.join("\r\n");
|
|
177
|
+
const bom = "\uFEFF";
|
|
178
|
+
const blob = new Blob([bom + csvString], {
|
|
179
|
+
type: "text/csv;charset=utf-8;"
|
|
180
|
+
});
|
|
181
|
+
const url = URL.createObjectURL(blob);
|
|
182
|
+
const link = document.createElement("a");
|
|
183
|
+
link.href = url;
|
|
184
|
+
const finalFileName = fileName.endsWith(".csv") || fileName.endsWith(".tsv") ? fileName : `${fileName}.csv`;
|
|
185
|
+
link.download = finalFileName;
|
|
186
|
+
link.click();
|
|
187
|
+
URL.revokeObjectURL(url);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/exportToPdf.ts
|
|
191
|
+
async function exportToPdf(table, options) {
|
|
192
|
+
const {
|
|
193
|
+
fileName = "export.pdf",
|
|
194
|
+
title,
|
|
195
|
+
scope = "filtered",
|
|
196
|
+
emptyBehavior = "skip",
|
|
197
|
+
orientation = "p",
|
|
198
|
+
fontFamily = "default"
|
|
199
|
+
} = options ?? {};
|
|
200
|
+
let jsPDF;
|
|
201
|
+
try {
|
|
202
|
+
const mod = await import('jspdf');
|
|
203
|
+
jsPDF = mod.default;
|
|
204
|
+
} catch {
|
|
205
|
+
throw new Error(
|
|
206
|
+
"[exportToPdf] jspdf is not installed. Run: npm install jspdf jspdf-autotable"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await import('jspdf-autotable');
|
|
211
|
+
} catch {
|
|
212
|
+
throw new Error(
|
|
213
|
+
"[exportToPdf] jspdf-autotable is not installed. Run: npm install jspdf jspdf-autotable"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const rows = getRowsByScope(table, scope);
|
|
217
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
218
|
+
console.warn("[grid-export] exportToPdf: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const headerGroups = table.getHeaderGroups();
|
|
222
|
+
const head = headerGroups.map(
|
|
223
|
+
(hg) => hg.headers.map((h) => {
|
|
224
|
+
if (h.isPlaceholder) return "";
|
|
225
|
+
return typeof h.column.columnDef.header === "string" ? h.column.columnDef.header : h.column.id;
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
const leafHeaders = table.getLeafHeaders();
|
|
229
|
+
const body = rows.map((row) => {
|
|
230
|
+
const cells = row.getVisibleCells();
|
|
231
|
+
return leafHeaders.map((lh) => {
|
|
232
|
+
const cell = cells.find((c) => c.column.id === lh.column.id);
|
|
233
|
+
return cell ? String(cell.getValue() ?? "") : "";
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
const doc = new jsPDF({ orientation, unit: "pt", format: "a4" });
|
|
237
|
+
if (fontFamily === "korean") {
|
|
238
|
+
const { loadKoreanFont: loadKoreanFont2 } = await Promise.resolve().then(() => (init_loadKoreanFont(), loadKoreanFont_exports));
|
|
239
|
+
await loadKoreanFont2(doc);
|
|
240
|
+
}
|
|
241
|
+
if (title) {
|
|
242
|
+
doc.text(title, 14, 20);
|
|
243
|
+
}
|
|
244
|
+
doc.autoTable({ head, body, startY: title ? 30 : 14 });
|
|
245
|
+
const normalized = fileName.endsWith(".pdf") ? fileName : `${fileName}.pdf`;
|
|
246
|
+
doc.save(normalized);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/copyToClipboard.ts
|
|
250
|
+
function escapeTsvValue(value) {
|
|
251
|
+
return value.replace(/[\t\r\n]/g, " ");
|
|
252
|
+
}
|
|
253
|
+
async function copyToClipboard(table, options) {
|
|
254
|
+
const { scope = "filtered", emptyBehavior = "skip" } = options ?? {};
|
|
255
|
+
const rows = getRowsByScope(table, scope);
|
|
256
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
257
|
+
console.warn("[grid-export] copyToClipboard: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const leafHeaders = table.getLeafHeaders();
|
|
261
|
+
const headerRow = leafHeaders.map((h) => {
|
|
262
|
+
const headerDef = h.column.columnDef.header;
|
|
263
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
264
|
+
return escapeTsvValue(text);
|
|
265
|
+
}).join(" ");
|
|
266
|
+
const dataRows = rows.map(
|
|
267
|
+
(row) => row.getVisibleCells().map((cell) => {
|
|
268
|
+
const value = cell.getValue();
|
|
269
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
270
|
+
return escapeTsvValue(str);
|
|
271
|
+
}).join(" ")
|
|
272
|
+
);
|
|
273
|
+
const tsvString = [headerRow, ...dataRows].join("\n");
|
|
274
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
275
|
+
await navigator.clipboard.writeText(tsvString);
|
|
276
|
+
} else {
|
|
277
|
+
const textarea = document.createElement("textarea");
|
|
278
|
+
textarea.value = tsvString;
|
|
279
|
+
textarea.style.position = "fixed";
|
|
280
|
+
textarea.style.opacity = "0";
|
|
281
|
+
document.body.appendChild(textarea);
|
|
282
|
+
textarea.select();
|
|
283
|
+
const success = document.execCommand("copy");
|
|
284
|
+
document.body.removeChild(textarea);
|
|
285
|
+
if (!success) {
|
|
286
|
+
throw new Error("[grid-export] copyToClipboard: Clipboard API not supported");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/printGrid.ts
|
|
292
|
+
function printGrid(table, options) {
|
|
293
|
+
const {
|
|
294
|
+
title,
|
|
295
|
+
scope = "filtered",
|
|
296
|
+
orientation = "p",
|
|
297
|
+
emptyBehavior = "skip"
|
|
298
|
+
} = options ?? {};
|
|
299
|
+
const rows = getRowsByScope(table, scope);
|
|
300
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
301
|
+
console.warn("[grid-export] printGrid: \uB0B4\uBCF4\uB0BC \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const leafHeaders = table.getLeafHeaders();
|
|
305
|
+
const headerHtml = leafHeaders.map((h) => {
|
|
306
|
+
const headerDef = h.column.columnDef.header;
|
|
307
|
+
const text = typeof headerDef === "string" ? headerDef : h.column.id;
|
|
308
|
+
return `<th>${text}</th>`;
|
|
309
|
+
}).join("");
|
|
310
|
+
const bodyHtml = rows.map((row) => {
|
|
311
|
+
const cells = row.getVisibleCells().map((cell) => {
|
|
312
|
+
const value = cell.getValue();
|
|
313
|
+
const str = value !== null && value !== void 0 ? String(value) : "";
|
|
314
|
+
return `<td>${str}</td>`;
|
|
315
|
+
}).join("");
|
|
316
|
+
return `<tr>${cells}</tr>`;
|
|
317
|
+
}).join("");
|
|
318
|
+
const orientationCss = orientation === "l" ? "landscape" : "portrait";
|
|
319
|
+
const titleHtml = title ? `<h2>${title}</h2>` : "";
|
|
320
|
+
const html = `<!DOCTYPE html>
|
|
321
|
+
<html>
|
|
322
|
+
<head>
|
|
323
|
+
<meta charset="utf-8">
|
|
324
|
+
<title>${title ?? ""}</title>
|
|
325
|
+
<style>
|
|
326
|
+
@page { size: ${orientationCss}; }
|
|
327
|
+
body { font-family: sans-serif; font-size: 12px; margin: 16px; }
|
|
328
|
+
h2 { font-size: 16px; margin-bottom: 12px; }
|
|
329
|
+
table { border-collapse: collapse; width: 100%; }
|
|
330
|
+
th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; page-break-inside: avoid; }
|
|
331
|
+
th { background: #f0f0f0; }
|
|
332
|
+
@media print {
|
|
333
|
+
body { margin: 0; }
|
|
334
|
+
}
|
|
335
|
+
</style>
|
|
336
|
+
</head>
|
|
337
|
+
<body>
|
|
338
|
+
${titleHtml}
|
|
339
|
+
<table>
|
|
340
|
+
<thead><tr>${headerHtml}</tr></thead>
|
|
341
|
+
<tbody>${bodyHtml}</tbody>
|
|
342
|
+
</table>
|
|
343
|
+
</body>
|
|
344
|
+
</html>`;
|
|
345
|
+
const popup = window.open("", "_blank");
|
|
346
|
+
if (!popup) {
|
|
347
|
+
console.warn(
|
|
348
|
+
"[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."
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
popup.onload = () => {
|
|
353
|
+
popup.print();
|
|
354
|
+
popup.close();
|
|
355
|
+
};
|
|
356
|
+
popup.document.write(html);
|
|
357
|
+
popup.document.close();
|
|
358
|
+
}
|
|
359
|
+
function formatValue(value, format) {
|
|
360
|
+
if (value == null) return "";
|
|
361
|
+
if (format === "date" || format === "datetime") {
|
|
362
|
+
const d = new Date(value);
|
|
363
|
+
if (isNaN(d.getTime())) return String(value);
|
|
364
|
+
return format === "datetime" ? d.toLocaleString("ko-KR") : d.toLocaleDateString("ko-KR");
|
|
365
|
+
}
|
|
366
|
+
if (format === "number" || format === "currency") {
|
|
367
|
+
const n = Number(value);
|
|
368
|
+
return isNaN(n) ? value : n;
|
|
369
|
+
}
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
function exportRowsToExcel(rows, columns, options) {
|
|
373
|
+
const {
|
|
374
|
+
fileName = "export.xlsx",
|
|
375
|
+
sheetName = "Sheet1",
|
|
376
|
+
emptyBehavior = "skip"
|
|
377
|
+
} = options ?? {};
|
|
378
|
+
if (rows.length === 0 && emptyBehavior === "skip") {
|
|
379
|
+
console.warn('[exportRowsToExcel] rows is empty \u2014 skipping file creation (emptyBehavior: "skip")');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const header = columns.map((c) => c.header);
|
|
383
|
+
const dataRows = rows.map(
|
|
384
|
+
(row) => columns.map((col) => formatValue(row[col.key], col.format))
|
|
385
|
+
);
|
|
386
|
+
const aoa = rows.length === 0 ? [header] : [header, ...dataRows];
|
|
387
|
+
const ws = XLSX2__namespace.utils.aoa_to_sheet(aoa);
|
|
388
|
+
ws["!cols"] = columns.map((c) => ({ wch: c.width ?? 15 }));
|
|
389
|
+
const headerRange = XLSX2__namespace.utils.decode_range(ws["!ref"] ?? "A1");
|
|
390
|
+
for (let c = headerRange.s.c; c <= headerRange.e.c; c++) {
|
|
391
|
+
const cellAddr = XLSX2__namespace.utils.encode_cell({ r: 0, c });
|
|
392
|
+
if (!ws[cellAddr]) continue;
|
|
393
|
+
ws[cellAddr].s = { font: { bold: true }, fill: { fgColor: { rgb: "F3F4F6" } } };
|
|
394
|
+
}
|
|
395
|
+
const wb = XLSX2__namespace.utils.book_new();
|
|
396
|
+
XLSX2__namespace.utils.book_append_sheet(wb, ws, sheetName);
|
|
397
|
+
const ext = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
|
|
398
|
+
XLSX2__namespace.writeFile(wb, ext);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
exports.copyToClipboard = copyToClipboard;
|
|
402
|
+
exports.exportRowsToExcel = exportRowsToExcel;
|
|
403
|
+
exports.exportToCSV = exportToCSV;
|
|
404
|
+
exports.exportToExcel = exportToExcel;
|
|
405
|
+
exports.exportToPdf = exportToPdf;
|
|
406
|
+
exports.printGrid = printGrid;
|
|
407
|
+
//# sourceMappingURL=index.cjs.map
|
|
408
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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","XLSX2"],"mappings":"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","file":"index.cjs","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"]}
|